@beweco/aurora-ui 1.0.0-beta.131 → 1.0.0-beta.133

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs.js CHANGED
@@ -857,6 +857,64 @@ const NAV_END_CLASS = `${CELL_CLASS} w-9 px-0 rounded-e-[12px] rounded-s-none`;
857
857
  * `--color-default` de Aurora (falso amigo, decisiones-aceptadas-f1.md D-F1-3).
858
858
  */
859
859
  const ACTIVE_LINK_CLASS = "rounded-[12px] bg-primary text-primary-foreground hover:bg-primary active:bg-primary";
860
+ /**
861
+ * Traducciones por defecto en español. Todas son `aria-label`: este componente no tiene más
862
+ * texto visible que los números de página.
863
+ */
864
+ const DEFAULT_TRANSLATIONS$f = {
865
+ previousPageAriaLabel: "Página anterior",
866
+ nextPageAriaLabel: "Página siguiente",
867
+ jumpBackwardAriaLabel: "Retroceder {count} páginas",
868
+ jumpForwardAriaLabel: "Avanzar {count} páginas",
869
+ };
870
+ /**
871
+ * TECH-2459 · elipsis navegable — páginas que salta la elipsis. Literal de v2:
872
+ * `dotsJump = 5`, default desestructurado en `@heroui/pagination@2.2.20`
873
+ * (`dist/use-pagination.js:57`, la versión que traía `@heroui/react@2.7.11`).
874
+ *
875
+ * El destino se recorta a `[1, total]` exactamente como allí (`dist/pagination.js:530`):
876
+ *
877
+ * onPress: () => isBefore
878
+ * ? setPage(activePage - dotsJump >= 1 ? activePage - dotsJump : 1)
879
+ * : setPage(activePage + dotsJump <= total ? activePage + dotsJump : total)
880
+ *
881
+ * Aquí lo hace `goTo`, que ya aplica el mismo clamp.
882
+ */
883
+ const DOTS_JUMP = 5;
884
+ /**
885
+ * El intercambio de iconos de la elipsis, tal cual lo declaraba el `tv` de v2
886
+ * (`@heroui/theme@2.4.15`, `dist/components/pagination.js:186-192`):
887
+ *
888
+ * forwardIcon: ["hidden", "group-hover:block",
889
+ * "group-data-[focus-visible=true]:block", "data-[before=true]:rotate-180"]
890
+ * ellipsis: "group-hover:hidden group-data-[focus-visible=true]:hidden"
891
+ *
892
+ * `data-focus-visible` SÍ sobrevive a v3 — no es de los atributos que react-aria renombró
893
+ * (`data-hover` → `data-hovered`, skill `auraui-components` trampa 3): el `Button` de
894
+ * react-aria-components lo escribe (`dist/private/Button.mjs:101`) y el propio
895
+ * `.pagination__link` de v3 lo usa (`@heroui/styles/dist/components/pagination.css:51-53`).
896
+ * El hover va por `group-hover:` (CSS `:hover`), igual que en ese mismo CSS.
897
+ */
898
+ const DOTS_REST_CLASS = "group-hover:hidden group-data-[focus-visible=true]:hidden";
899
+ const DOTS_JUMP_ICON_CLASS = "hidden group-hover:block group-data-[focus-visible=true]:block data-[before=true]:rotate-180";
900
+ /**
901
+ * Los tres puntos en reposo: copia literal del `EllipsisIcon` de v2
902
+ * (`@heroui/shared-icons@2.1.9`, `dist/ellipsis.js`) — tres `circle` de `r=1` en (5,12),
903
+ * (12,12) y (19,12) sobre un `viewBox` de 24 y `1em` de lado.
904
+ *
905
+ * NO vale el `…` (U+2026) que pinta `Pagination.Ellipsis` de v3
906
+ * (`@heroui/react/dist/components/pagination/pagination.js:231`, children hardcodeado y
907
+ * posterior al spread de props, o sea imposible de sustituir): ese glifo se apoya en la línea
908
+ * base y queda más bajo que los puntos de v2. Medido sobre
909
+ * `vrt/__baselines__/custom-components-pagination--default-light.png`.
910
+ */
911
+ const EllipsisDotsIcon = () => (jsxRuntime.jsxs("svg", { "aria-hidden": "true", className: DOTS_REST_CLASS, fill: "none", height: "1em", shapeRendering: "geometricPrecision", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "1.5", viewBox: "0 0 24 24", width: "1em", children: [jsxRuntime.jsx("circle", { cx: "12", cy: "12", fill: "currentColor", r: "1" }), jsxRuntime.jsx("circle", { cx: "19", cy: "12", fill: "currentColor", r: "1" }), jsxRuntime.jsx("circle", { cx: "5", cy: "12", fill: "currentColor", r: "1" })] }));
912
+ /**
913
+ * El doble galón que v2 mostraba al pasar el ratón: `ForwardIcon` de
914
+ * `@heroui/shared-icons@2.1.9` (`dist/forward.js`), los dos trazos `M13 17l5-5-5-5` y
915
+ * `M6 17l5-5-5-5` → `»`. Con `data-before="true"` gira 180° y se lee `«`, igual que en v2.
916
+ */
917
+ const JumpIcon = ({ isBefore }) => (jsxRuntime.jsxs("svg", { "aria-hidden": "true", className: DOTS_JUMP_ICON_CLASS, "data-before": isBefore ? "true" : undefined, fill: "none", focusable: "false", height: "1em", role: "presentation", shapeRendering: "geometricPrecision", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "1.5", viewBox: "0 0 24 24", width: "1em", children: [jsxRuntime.jsx("path", { d: "M13 17l5-5-5-5" }), jsxRuntime.jsx("path", { d: "M6 17l5-5-5-5" })] }));
860
918
  /**
861
919
  * Rango de páginas con elipsis, **igual que el algoritmo de HeroUI v2** (v3 no trae ninguno: sus
862
920
  * piezas son puramente presentacionales). Reproduce `siblings: 1` y `boundaries: 1`, sus defaults.
@@ -902,14 +960,23 @@ function getPaginationRange(total, current) {
902
960
  * `date-picker` en D-G1-3). Esta fachada reconstruye el comportamiento con estado (rango de
903
961
  * páginas + elipsis) manteniendo la API pública v2 (`total`/`page`/`onChange`).
904
962
  *
963
+ * Reconstruido además (reportado desde `bewe-analytics`, tabla de 34 páginas):
964
+ * - **La elipsis vuelve a ser un control**: salta ±`DOTS_JUMP` (5) páginas y al pasar el ratón
965
+ * o recibir foco de teclado cambia los tres puntos por el doble galón `«`/`»`, como v2. Las
966
+ * apps no pueden arreglarlo por su lado: esta fachada compone la paginación por dentro.
967
+ * - **Nombre accesible en anterior/siguiente**: sus galones son `aria-hidden` en v3, así que sin
968
+ * `aria-label` los dos botones llegaban vacíos al árbol de accesibilidad.
969
+ *
970
+
905
971
  * ⚠️ `size` se acepta pero no se aplica: mismo bug preexistente que v2 (destructurado, nunca
906
972
  * reenviado a HeroUI) — fuera de scope de esta migración, reportado aparte (ver
907
973
  * findings/decisiones-aceptadas-g2.md y f5-L7-sueltos.md · P-17). v3 SÍ tiene la prop (emite
908
974
  * `.pagination--sm|--md|--lg`), así que arreglarlo es un cambio de tamaño visible: ticket aparte.
909
975
  */
910
- const Pagination = ({ total, page, initialPage = 1, onChange, size: _size = "sm", className, isDisabled, }) => {
976
+ const Pagination = ({ total, page, initialPage = 1, onChange, size: _size = "sm", className, isDisabled, translations = {}, }) => {
911
977
  const [internalPage, setInternalPage] = $8WNJc$react.useState(initialPage);
912
978
  const currentPage = page ?? internalPage;
979
+ const t = { ...DEFAULT_TRANSLATIONS$f, ...translations };
913
980
  const goTo = (next) => {
914
981
  const clamped = Math.min(Math.max(next, 1), total);
915
982
  if (page === undefined) {
@@ -918,13 +985,38 @@ const Pagination = ({ total, page, initialPage = 1, onChange, size: _size = "sm"
918
985
  onChange?.(clamped);
919
986
  };
920
987
  const items = getPaginationRange(total, currentPage);
988
+ // Qué elipsis retrocede y cuál avanza, con el mismo criterio de v2:
989
+ // `isBefore = index < range.indexOf(activePage)` (`@heroui/pagination@2.2.20`,
990
+ // `dist/pagination.js:464`). Si `page` viene fuera de rango, `indexOf` da -1 y todas las
991
+ // elipsis avanzan — también igual que v2, que no protegía ese caso.
992
+ const activeIndex = items.indexOf(currentPage);
921
993
  return (
922
994
  // `w-fit` reproduce el `max-w-fit` del wrapper de v2: `.pagination` de v3 es `w-full` +
923
995
  // `justify-between`, así que dentro de un `flex justify-center` (como el de AccordionList) la
924
996
  // tira se quedaba pegada a la izquierda en vez de centrarse.
925
- jsxRuntime.jsx(react.Pagination, { className: ["w-fit", className].filter(Boolean).join(" "), children: jsxRuntime.jsxs(react.Pagination.Content, { className: STRIP_CLASS, children: [jsxRuntime.jsx(react.Pagination.Item, { children: jsxRuntime.jsx(react.Pagination.Previous, { className: NAV_START_CLASS, isDisabled: isDisabled || currentPage <= 1, onPress: () => goTo(currentPage - 1), children: jsxRuntime.jsx(react.Pagination.PreviousIcon, {}) }) }), items.map((item, index) => item === "ellipsis" ? (
926
- // biome-ignore lint/suspicious/noArrayIndexKey: solo hay como máximo 2 elipsis, posición estable
927
- jsxRuntime.jsx(react.Pagination.Item, { children: jsxRuntime.jsx(react.Pagination.Ellipsis, { className: `${CELL_CLASS} ${INNER_RADIUS_CLASS}` }) }, `ellipsis-${index}`)) : (jsxRuntime.jsx(react.Pagination.Item, { children: jsxRuntime.jsx(react.Pagination.Link, { className: `${CELL_CLASS} ${item === currentPage ? ACTIVE_LINK_CLASS : INNER_RADIUS_CLASS}`, isActive: item === currentPage, isDisabled: isDisabled, onPress: () => goTo(item), children: item }) }, item))), jsxRuntime.jsx(react.Pagination.Item, { children: jsxRuntime.jsx(react.Pagination.Next, { className: NAV_END_CLASS, isDisabled: isDisabled || currentPage >= total, onPress: () => goTo(currentPage + 1), children: jsxRuntime.jsx(react.Pagination.NextIcon, {}) }) })] }) }));
997
+ jsxRuntime.jsx(react.Pagination, { className: ["w-fit", className].filter(Boolean).join(" "), children: jsxRuntime.jsxs(react.Pagination.Content, { className: STRIP_CLASS, children: [jsxRuntime.jsx(react.Pagination.Item, { children: jsxRuntime.jsx(react.Pagination.Previous
998
+ // v3 mete el galón en un `<span aria-hidden="true">`
999
+ // (`pagination.js:165`), así que sin esto el botón se queda SIN nombre
1000
+ // accesible: un lector de pantalla lo anuncia como "botón" a secas. v2 sí
1001
+ // lo nombraba ("previous page button", `use-pagination.js:213`), pero en
1002
+ // inglés y hardcodeado; aquí va por `translations` (regla obligatoria de
1003
+ // i18n del repo, skill `auraui-i18n`).
1004
+ , { "aria-label": t.previousPageAriaLabel, className: NAV_START_CLASS, isDisabled: isDisabled || currentPage <= 1, onPress: () => goTo(currentPage - 1), children: jsxRuntime.jsx(react.Pagination.PreviousIcon, {}) }) }), items.map((item, index) => {
1005
+ if (item === "ellipsis") {
1006
+ const isBefore = index < activeIndex;
1007
+ const label = isBefore
1008
+ ? t.jumpBackwardAriaLabel
1009
+ : t.jumpForwardAriaLabel;
1010
+ return (
1011
+ // biome-ignore lint/suspicious/noArrayIndexKey: solo hay como máximo 2 elipsis, posición estable
1012
+ jsxRuntime.jsx(react.Pagination.Item, { children: jsxRuntime.jsxs(react.Pagination.Link, { "aria-label": label.replace("{count}", String(DOTS_JUMP)), className: `group ${CELL_CLASS} ${INNER_RADIUS_CLASS}`, isDisabled: isDisabled, onPress: () => goTo(isBefore
1013
+ ? currentPage - DOTS_JUMP
1014
+ : currentPage + DOTS_JUMP), children: [jsxRuntime.jsx(EllipsisDotsIcon, {}), jsxRuntime.jsx(JumpIcon, { isBefore: isBefore })] }) }, `ellipsis-${index}`));
1015
+ }
1016
+ return (jsxRuntime.jsx(react.Pagination.Item, { children: jsxRuntime.jsx(react.Pagination.Link, { className: `${CELL_CLASS} ${item === currentPage ? ACTIVE_LINK_CLASS : INNER_RADIUS_CLASS}`, isActive: item === currentPage, isDisabled: isDisabled, onPress: () => goTo(item), children: item }) }, item));
1017
+ }), jsxRuntime.jsx(react.Pagination.Item, { children: jsxRuntime.jsx(react.Pagination.Next
1018
+ /* Mismo motivo que en `Previous`: el galón es `aria-hidden`. */
1019
+ , { "aria-label": t.nextPageAriaLabel, className: NAV_END_CLASS, isDisabled: isDisabled || currentPage >= total, onPress: () => goTo(currentPage + 1), children: jsxRuntime.jsx(react.Pagination.NextIcon, {}) }) })] }) }));
928
1020
  };
929
1021
 
930
1022
  // TECH-2459 · F5 · P-01 — Valores tomados del tema v2 REAL (`@heroui/theme@2.4.15`,
@@ -1501,9 +1593,13 @@ const FIELD_SHADOW_CLASS$2 = "shadow-xs";
1501
1593
  // borde transparente de 2px NO es inocuo, porque con `box-sizing: border-box` come 2px por
1502
1594
  // lado y empuja el texto (medido: la sangría salía 14px en vez de los 12px de la referencia).
1503
1595
  // 2. `underlined` usa `!px-1` (4px), no el padding del tamaño, y su sombra es
1504
- // `0 1px 0 0 rgba(0,0,0,.05)` en vez de `shadow-sm`. Van con `!` porque estas clases NO pasan
1505
- // por `cn`: se concatenan a pelo, así que entre `px-3` y `px-1` no decide el orden del array
1506
- // sino el orden del CSS generado (medido: ganaba `px-3`).
1596
+ // `0 1px 0 0 rgba(0,0,0,.05)` en vez de `shadow-sm`. **El `!` sigue siendo imprescindible aunque
1597
+ // estas clases ya pasen por `cn`**: tailwind-merge 3 trata la forma con `!` como un grupo
1598
+ // APARTE de la forma sin `!`, así que NO resuelve `px-3` contra `px-1!` — medido con el `cn`
1599
+ // real del paquete instalado, `cn("px-3","px-1!")` → `"px-3 px-1!"`, las dos en el DOM. Quien
1600
+ // decide es entonces la `!important`, no el orden del array ni el del CSS generado (sin el `!`
1601
+ // ganaba `px-3`; medido computado con el `!`: `padding-inline: 4px`). No lo quites al ver que la
1602
+ // cadena ya se fusiona.
1507
1603
  // 3. `isInvalid` en v2 teñía TODO de `danger` —borde, label y texto/placeholder—, no solo el
1508
1604
  // borde; y en `flat` cambiaba el fondo a `danger-50`.
1509
1605
  // ⚠️ `bordered` NO lleva fondo, a propósito (TECH-2459 · F5, hallazgo de S2 + queja directa del
@@ -1531,6 +1627,44 @@ const VARIANT_CLASS$3 = {
1531
1627
  // píxeles del placeholder son `113,113,122` (el gris `default-500`), no el rosa `243,18,96`.
1532
1628
  const INVALID_LABEL_CLASS$2 = "text-danger";
1533
1629
  const INVALID_TEXT_CLASS$2 = "text-danger";
1630
+ // ── El `className` del consumidor: FUSIONADO y en la RAÍZ (TECH-2459 · P-?) ──────────────────
1631
+ //
1632
+ // Dos defectos en uno, y los dos se veían igual: el ancho que pide la app no llegaba nunca.
1633
+ //
1634
+ // 1. **Se concatenaba en vez de fusionarse.** Esta fachada montaba sus `className` con
1635
+ // `[...].filter(Boolean).join(" ")`, así que `w-full` (de `fullWidth`, que por defecto es
1636
+ // `true`) y el `w-48` del consumidor llegaban LOS DOS al DOM y decidía el orden del CSS
1637
+ // emitido, no el del array — y gana `w-full`. Medido en el banco de pruebas: un campo con
1638
+ // `className="w-48"` dentro de una fila flex de 592px medía 290px, exactamente lo mismo que su
1639
+ // hermano sin `className`; en un bloque de 1287px, un `w-56` medía 1287px.
1640
+ // v2 NO se comportaba así: `useInput` (`@heroui/input@2.4.22`,
1641
+ // `dist/chunk-F2LJ3HQE.mjs:68,166`) metía el `className` del consumidor en
1642
+ // `slots.base({ class: baseStyles })`, y el `tv` de v2 lleva `twMerge: true` por defecto
1643
+ // (`@heroui/theme@2.4.15`, `dist/utils/tv.js:56`) → tailwind-merge BORRABA el `w-full` y el
1644
+ // ancho del consumidor ganaba. Por eso aquí se usa `cn` (mismo tailwind-merge, vía
1645
+ // `tailwind-variants`) con el `className` del consumidor en ÚLTIMA posición, igual que las 33
1646
+ // fachadas del repo que ya lo hacen (`Chip.tsx:398`, `Checkbox.tsx:224`).
1647
+ //
1648
+ // 2. **Aterrizaba en la pieza equivocada.** El `className` se emitía en el `InputGroup` (el
1649
+ // equivalente del slot `inputWrapper` de v2), no en la raíz. En v2 iba al slot `base`
1650
+ // —`"group flex flex-col data-[hidden=true]:hidden"` + el `w-full` de `fullWidth`
1651
+ // (`@heroui/theme@2.4.15`, `dist/components/input.js:7452`)—, que es justo donde vive el ancho.
1652
+ // Fusionar en el `InputGroup` no habría bastado: la raíz seguiría con su `w-full` y el campo
1653
+ // quedaría a 192px DESBORDANDO un hueco de 83. Así que el `className` se muda a la raíz, que es
1654
+ // donde lo tenía v2; quien quiera tocar la caja del campo sigue teniendo `classNames.inputWrapper`,
1655
+ // el mismo canal que en v2 (10+ usos reales en las apps: `bg-content1`, `h-9`, `shadow-none`…).
1656
+ //
1657
+ // Consecuencia del punto 2: el `w-full` del `InputGroup` deja de depender de `fullWidth` y pasa a
1658
+ // ser incondicional — que es LITERALMENTE lo que tenía v2 (`inputWrapper: "relative w-full
1659
+ // inline-flex …"`, sin variante que lo condicione). `fullWidth` decide solo el ancho de la RAÍZ,
1660
+ // como en su `tv`. Sin esto, un `fullWidth={false}` con un `w-48` del consumidor dejaría la caja
1661
+ // del campo encogida a su contenido dentro de una raíz de 192px.
1662
+ //
1663
+ // ⚠️ Lo que `cn` NO resuelve, y por eso el `!` de la variante `underlined` sigue siendo necesario:
1664
+ // tailwind-merge 3 trata la forma con `!` como un grupo APARTE de la forma sin `!`, así que
1665
+ // `px-3` y `px-1!` sobreviven las dos (comprobado con el `cn` real del paquete instalado) y sigue
1666
+ // ganando la `!important`. Está anotado también en el punto 2 de `VARIANT_CLASS`, que es donde se
1667
+ // lee al tocar esas clases.
1534
1668
  /**
1535
1669
  * Input genérico reutilizable basado en HeroUI v3 (TextField + InputGroup + Input) siguiendo las
1536
1670
  * reglas de diseño BeweOS (TECH-2459 · F3.0).
@@ -1597,45 +1731,14 @@ fullWidth = true, value, defaultValue, onChange, onValueChange, className, class
1597
1731
  onChange(fakeEvent);
1598
1732
  }
1599
1733
  };
1600
- return (jsxRuntime.jsxs(react.TextField, { id: inputId, name: name, isInvalid: isInvalid, isRequired: isRequired, isDisabled: isDisabled, isReadOnly: isReadOnly, className: [FIELD_STACK_CLASS$3[size], fullWidth ? "w-full" : ""]
1601
- .filter(Boolean)
1602
- .join(" "), children: [label && (jsxRuntime.jsx(react.Label, { className: [
1603
- LABEL_CLASS$4,
1604
- LABEL_LEFT_CLASS[variant],
1605
- variant === "underlined" ? "top-0" : LABEL_OFFSET_CLASS$1[size],
1606
- isInvalid ? INVALID_LABEL_CLASS$2 : "",
1607
- labelFlotante ? LABEL_INSIDE_CLASS[size] : "",
1608
- labelFlotante ? LABEL_FLOAT_CLASS : "",
1609
- classNames?.label,
1610
- ]
1611
- .filter(Boolean)
1612
- .join(" "), children: label })), jsxRuntime.jsxs(react.InputGroup, { isInvalid: isInvalid, isDisabled: isDisabled, isReadOnly: isReadOnly, className: [
1613
- SIZE_CLASS$4[size],
1614
- radius === "full" ? PADDING_FULL_CLASS$1[size] : PADDING_CLASS$2[size],
1615
- variant === "underlined" ? UNDERLINED_INNER_CLASS$1[size] : "",
1616
- RADIUS_CLASS$6[radius],
1617
- VARIANT_CLASS$3[variant],
1618
- FIELD_FOCUS_CLASS$3,
1619
- FIELD_SHADOW_CLASS$2,
1620
- fullWidth ? "w-full" : "",
1621
- classNames?.inputWrapper,
1622
- className,
1623
- ]
1624
- .filter(Boolean)
1625
- .join(" "), children: [startContent && (jsxRuntime.jsx(react.InputGroup.Prefix, { className: "border-none bg-transparent px-0", children: startContent })), jsxRuntime.jsx(react.InputGroup.Input, { ...rest, ref: ref, required: required, type: type, placeholder: labelFlotante ? " " : placeholder, value: value, defaultValue: defaultValue, onChange: handleChange, className: [
1626
- "bg-transparent p-0",
1627
- SIZE_TEXT_CLASS$1[size],
1628
- isInvalid ? INVALID_TEXT_CLASS$2 : "",
1629
- startContent ? "ps-1.5" : "",
1630
- endContent || showClear ? "pe-1.5" : "",
1631
- classNames?.input,
1632
- ]
1633
- .filter(Boolean)
1634
- .join(" ") }), showClear && (jsxRuntime.jsx(react.CloseButton, { "aria-label": "Limpiar", className: "h-4 w-4 min-h-0 p-0.5 mr-1", onPress: handleClear })), endContent && (jsxRuntime.jsx(react.InputGroup.Suffix, { className: "border-none bg-transparent px-0", children: endContent }))] }), description && !isInvalid && (jsxRuntime.jsx(react.Description, { className: [HELPER_CLASS$2, "text-left", classNames?.description]
1635
- .filter(Boolean)
1636
- .join(" "), children: description })), isInvalid && errorMessage && (jsxRuntime.jsx(react.FieldError, { className: [HELPER_CLASS$2, "text-left", classNames?.errorMessage]
1637
- .filter(Boolean)
1638
- .join(" "), children: errorMessage }))] }));
1734
+ return (jsxRuntime.jsxs(react.TextField, { id: inputId, name: name, isInvalid: isInvalid, isRequired: isRequired, isDisabled: isDisabled, isReadOnly: isReadOnly, className: react.cn(FIELD_STACK_CLASS$3[size], fullWidth ? "w-full" : "",
1735
+ // El `className` del consumidor, en la raíz y en último lugar: el slot `base` de
1736
+ // v2 y la posición que le deja ganar en tailwind-merge (ver bloque de arriba).
1737
+ className), children: [label && (jsxRuntime.jsx(react.Label, { className: react.cn(LABEL_CLASS$4, LABEL_LEFT_CLASS[variant], variant === "underlined" ? "top-0" : LABEL_OFFSET_CLASS$1[size], isInvalid ? INVALID_LABEL_CLASS$2 : "", labelFlotante ? LABEL_INSIDE_CLASS[size] : "", labelFlotante ? LABEL_FLOAT_CLASS : "", classNames?.label), children: label })), jsxRuntime.jsxs(react.InputGroup, { isInvalid: isInvalid, isDisabled: isDisabled, isReadOnly: isReadOnly, className: react.cn(SIZE_CLASS$4[size], radius === "full" ? PADDING_FULL_CLASS$1[size] : PADDING_CLASS$2[size], variant === "underlined" ? UNDERLINED_INNER_CLASS$1[size] : "", RADIUS_CLASS$6[radius], VARIANT_CLASS$3[variant], FIELD_FOCUS_CLASS$3, FIELD_SHADOW_CLASS$2,
1738
+ // Incondicional, como el `inputWrapper` de v2: la caja del campo llena
1739
+ // siempre su raíz, y es la raíz la que responde a `fullWidth` y al ancho que
1740
+ // pase el consumidor.
1741
+ "w-full", 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: ref, required: required, type: type, placeholder: labelFlotante ? " " : placeholder, value: value, defaultValue: defaultValue, onChange: handleChange, className: react.cn("bg-transparent p-0", SIZE_TEXT_CLASS$1[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: "h-4 w-4 min-h-0 p-0.5 mr-1", 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?.description), children: description })), isInvalid && errorMessage && (jsxRuntime.jsx(react.FieldError, { className: react.cn(HELPER_CLASS$2, "text-left", classNames?.errorMessage), children: errorMessage }))] }));
1639
1742
  });
1640
1743
  Input.displayName = "Input";
1641
1744
 
@@ -5596,7 +5699,17 @@ const MenuMobileBottomBar = $8WNJc$react.memo(function MenuMobileBottomBar({ ite
5596
5699
  // `flex group gap-2 items-center justify-between … px-2 py-1.5 w-full`). `.list-box-item` de v3
5597
5700
  // es `justify-start gap-3`, así que el `endContent` (el `+`, el chip `New`, el chip numérico)
5598
5701
  // dejaba de irse al borde derecho y quedaba pegado al texto, y la etiqueta se corría 4px.
5599
- const ITEM_BASE_CLASS = "px-3 min-h-11 rounded-[14px] h-[44px] gap-2 justify-between data-[selected=true]:bg-primary-100";
5702
+ //
5703
+ // TECH-2459 · `group` — misma cita de v2 que la de arriba: su slot `base` empezaba por
5704
+ // `flex group …`. Es la pieza de la que dependen los CUATRO `group-data-[selected=true]:*` de
5705
+ // este fichero (la etiqueta de primer nivel en `ITEM_TITLE_CLASS` y los tres iconos). Sin un
5706
+ // ancestro con la clase `group`, esa variante de Tailwind no casa NUNCA y las cuatro reglas son
5707
+ // código muerto: medido en el navegador, el ítem de primer nivel SELECCIONADO se quedaba en
5708
+ // `default-500` (#71717a claro / #a1a1aa oscuro) donde v2 lo pintaba en `default-600`
5709
+ // (#52525b / #d4d4d8), así que el ítem activo no se distinguía de sus hermanos más que por la
5710
+ // píldora de fondo. No lo delata ni el compilador ni un grep del código: la clase está escrita
5711
+ // y es correcta, solo le falta el ancla.
5712
+ const ITEM_BASE_CLASS = "group px-3 min-h-11 rounded-[14px] h-[44px] gap-2 justify-between data-[selected=true]:bg-primary-100";
5600
5713
  // TECH-2459 · F5 · P-13a — override del ítem COLAPSADO. Venía de `getItemClasses(isCollapsed)`
5601
5714
  // (`MenuNavList.styles.ts` de v2, fichero que la migración eliminó): `w-11 h-11 gap-0 p-0`. Es lo
5602
5715
  // que centraba el icono: un cuadrado exacto de 44×44 SIN padding. Sin él, el ítem colapsado
@@ -5740,7 +5853,7 @@ const MenuNavList = $8WNJc$react.forwardRef(({ items, isCollapsed, defaultSelect
5740
5853
  // `MenuClawNavItem` (HTML semántico + `onClick`, sin `ListBox`).
5741
5854
  const renderSubItem = $8WNJc$react.useCallback((item) => {
5742
5855
  const isSelected = item.key === selectedKey;
5743
- return (jsxRuntime.jsx("li", { children: jsxRuntime.jsxs("button", { type: "button", "data-menu-subitem-selected": isSelected ? "true" : "false", "aria-current": isSelected ? "page" : undefined, className: react.cn("flex w-full items-center justify-between gap-2 min-h-9 rounded-[14px] px-3 py-2 menu-nav-list__sub-item text-left", "text-default-500 hover:text-default-700 transition-colors outline-none", "focus-visible:z-10 focus-visible:outline-2 focus-visible:outline-focus focus-visible:outline-offset-2", isSelected && "selected"), onClick: () => handleItemPress(item), children: [jsxRuntime.jsx(react.Label, { children: item.title }), item.endContent ?? null] }) }, item.key));
5856
+ return (jsxRuntime.jsx("li", { children: jsxRuntime.jsxs("button", { type: "button", "data-menu-subitem-selected": isSelected ? "true" : "false", "aria-current": isSelected ? "page" : undefined, className: react.cn("flex w-full items-center justify-between gap-2 min-h-9 rounded-[14px] px-3 py-2 menu-nav-list__sub-item text-left", "text-default-500 hover:text-default-700 transition-colors outline-none", "focus-visible:z-10 focus-visible:outline-2 focus-visible:outline-focus focus-visible:outline-offset-2", isSelected && "selected"), onClick: () => handleItemPress(item), children: [jsxRuntime.jsx(react.Label, { className: "text-sm font-normal text-inherit", children: item.title }), item.endContent ?? null] }) }, item.key));
5744
5857
  }, [handleItemPress, selectedKey]);
5745
5858
  // Renders a nested item, i.e., an item that has sub-items.
5746
5859
  // biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
@@ -8630,9 +8743,11 @@ const FIELD_STACK_CLASS$1 = {
8630
8743
  * metido en un hueco estrecho), no una reposición. El solape del chevron se arregla en
8631
8744
  * `VALUE_ROOM_CLASS`, que es donde v2 lo tenía resuelto.
8632
8745
  *
8633
- * Va antes del `className` del consumidor: quien necesite otro mínimo pasa su propio `min-w-*` y,
8634
- * al declarar las dos la misma propiedad con la misma especificidad, gana la que Tailwind 4 emita
8635
- * después (escala ascendente) — mismo comportamiento que tenía el `min-w-40` del `tv` de v2.
8746
+ * Va antes del `className` del consumidor, y ahora las dos pasan por `cn`: quien necesite otro
8747
+ * mínimo pasa su propio `min-w-*` y tailwind-merge BORRA este — comprobado con el `cn` real
8748
+ * (`cn("min-w-40","min-w-[180px]")` → `"min-w-[180px]"`). Antes las dos llegaban al DOM y decidía
8749
+ * el orden en que Tailwind 4 las emitiera, no el del array; es el mismo mecanismo que tenía el
8750
+ * `min-w-40` del `tv` de v2, que también fusionaba.
8636
8751
  */
8637
8752
  const ROOT_MIN_WIDTH_CLASS = "min-w-40";
8638
8753
  // v3 no tiene el vocabulario `variant` de v2 (solo "primary"/"secondary", sin borde visible por
@@ -8643,9 +8758,13 @@ const ROOT_MIN_WIDTH_CLASS = "min-w-40";
8643
8758
  // borde transparente de 2px NO es inocuo, porque con `box-sizing: border-box` come 2px por
8644
8759
  // lado y empuja el texto (medido: la sangría salía 14px en vez de los 12px de la referencia).
8645
8760
  // 2. `underlined` usa `!px-1` (4px), no el padding del tamaño, y su sombra es
8646
- // `0 1px 0 0 rgba(0,0,0,.05)` en vez de `shadow-sm`. Van con `!` porque estas clases NO pasan
8647
- // por `cn`: se concatenan a pelo, así que entre `px-3` y `px-1` no decide el orden del array
8648
- // sino el orden del CSS generado (medido: ganaba `px-3`).
8761
+ // `0 1px 0 0 rgba(0,0,0,.05)` en vez de `shadow-sm`. **El `!` sigue siendo imprescindible aunque
8762
+ // estas clases ya pasen por `cn`**: tailwind-merge 3 trata la forma con `!` como un grupo
8763
+ // APARTE de la forma sin `!`, así que NO resuelve `px-3` contra `px-1!` — medido con el `cn`
8764
+ // real del paquete instalado, `cn("px-3","px-1!")` → `"px-3 px-1!"`, las dos en el DOM. Quien
8765
+ // decide es entonces la `!important`, no el orden del array ni el del CSS generado (sin el `!`
8766
+ // ganaba `px-3`; medido computado con el `!`: `padding-inline: 4px`). No lo quites al ver que la
8767
+ // cadena ya se fusiona.
8649
8768
  // 3. `isInvalid` en v2 teñía TODO de `danger` —borde, label y texto/placeholder—, no solo el
8650
8769
  // borde; y en `flat` cambiaba el fondo a `danger-50`.
8651
8770
  // ⚠️ `bordered` NO lleva fondo, a propósito (TECH-2459 · F5, hallazgo de S2 + queja directa del
@@ -8781,49 +8900,39 @@ placeholder = "", selectionMode = "single", selectedKeys, defaultSelectedKeys, o
8781
8900
  // `undefined` y el campo dejaba de llenar su contenedor: en una fila flex se encogía al ancho
8782
8901
  // de su contenido (medido por S2: 208px dentro de una fila de 592, 336px de hueco). En una
8783
8902
  // columna no se notaba porque el `stretch` del flex lo tapaba — pero afecta a cualquier app.
8784
- fullWidth = true, isOpen, defaultOpen, onOpenChange, className, classNames, }) => {
8903
+ fullWidth = true, isOpen, defaultOpen, onOpenChange, className, classNames, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, "aria-details": ariaDetails, }) => {
8785
8904
  const generatedId = $8WNJc$react.useId();
8786
8905
  const selectId = id || generatedId;
8787
8906
  const requiredField = isRequired;
8788
8907
  // Mismo criterio que el `group-data-[has-helper=true]` de v2: cuenta como "con ayuda" el campo
8789
8908
  // que efectivamente PINTA una descripción o un mensaje de error, no el que solo recibe la prop.
8790
8909
  const hasHelper = Boolean((description && !isInvalid) || (isInvalid && errorMessage));
8791
- const triggerClassName = [
8792
- SIZE_CLASS$1[size],
8793
- radius === "full" ? PADDING_FULL_CLASS[size] : PADDING_CLASS[size],
8794
- TRIGGER_ALIGN_CLASS,
8795
- RADIUS_CLASS$4[radius],
8796
- VARIANT_CLASS$1[variant],
8797
- // En v2 el estado inválido MANDABA también con el campo enfocado
8798
- // (`group-data-[focus=true]:!border-danger`), así que el borde de foco no se aplica cuando
8799
- // el campo está en error: se queda el `danger`.
8800
- isInvalid ? INVALID_VARIANT_CLASS[variant] : FOCUS_BORDER_CLASS,
8801
- FIELD_FOCUS_CLASS$1,
8802
- FIELD_SHADOW_CLASS$1,
8803
- fullWidth ? "w-full" : "",
8804
- classNames?.trigger,
8805
- ]
8806
- .filter(Boolean)
8807
- .join(" ");
8808
- const inner = (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [label && (jsxRuntime.jsx(react.Label, { className: [
8809
- LABEL_CLASS$1,
8810
- hasHelper
8811
- ? LABEL_OFFSET_HELPER_CLASS[size]
8812
- : LABEL_OFFSET_CLASS[size],
8813
- isInvalid ? INVALID_LABEL_CLASS$1 : "",
8814
- classNames?.label,
8815
- ]
8816
- .filter(Boolean)
8817
- .join(" "), children: label })), jsxRuntime.jsxs(react.Select.Trigger, { className: triggerClassName, children: [jsxRuntime.jsx(react.Select.Value, { className: [
8818
- SIZE_TEXT_CLASS[size],
8819
- VALUE_ROOM_CLASS,
8820
- isInvalid ? INVALID_TEXT_CLASS$1 : "",
8821
- ]
8822
- .filter(Boolean)
8823
- .join(" ") }), 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, { children: withItemIndicator(withCollectionItemIds(children)) }) }), isInvalid && errorMessage && (jsxRuntime.jsx(react.FieldError, { className: `${HELPER_CLASS$1} text-left`, children: errorMessage }))] }));
8910
+ const triggerClassName = react.cn(SIZE_CLASS$1[size], radius === "full" ? PADDING_FULL_CLASS[size] : PADDING_CLASS[size], TRIGGER_ALIGN_CLASS, RADIUS_CLASS$4[radius], VARIANT_CLASS$1[variant],
8911
+ // En v2 el estado inválido MANDABA también con el campo enfocado
8912
+ // (`group-data-[focus=true]:!border-danger`), así que el borde de foco no se aplica cuando
8913
+ // el campo está en error: se queda el `danger`.
8914
+ isInvalid ? INVALID_VARIANT_CLASS[variant] : FOCUS_BORDER_CLASS, FIELD_FOCUS_CLASS$1, FIELD_SHADOW_CLASS$1,
8915
+ // Incondicional, como el slot `trigger` de v2 (`"relative px-3 gap-3 w-full inline-flex …"`,
8916
+ // sin variante que lo condicione): el disparador llena siempre su raíz, y es la raíz la que
8917
+ // responde a `fullWidth` y al ancho que pase el consumidor. v3 hace lo mismo por su cuenta
8918
+ // con `.select__trigger--full-width`, pero esa regla vive en la capa `components` y la pisa
8919
+ // cualquier utilidad.
8920
+ "w-full", classNames?.trigger);
8921
+ const inner = (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [label && (jsxRuntime.jsx(react.Label, { className: react.cn(LABEL_CLASS$1, hasHelper
8922
+ ? LABEL_OFFSET_HELPER_CLASS[size]
8923
+ : LABEL_OFFSET_CLASS[size], isInvalid ? INVALID_LABEL_CLASS$1 : "", classNames?.label), children: label })), jsxRuntime.jsxs(react.Select.Trigger, { className: triggerClassName, children: [jsxRuntime.jsx(react.Select.Value, { className: react.cn(SIZE_TEXT_CLASS[size], VALUE_ROOM_CLASS, isInvalid ? INVALID_TEXT_CLASS$1 : "") }), 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, { children: withItemIndicator(withCollectionItemIds(children)) }) }), isInvalid && errorMessage && (jsxRuntime.jsx(react.FieldError, { className: `${HELPER_CLASS$1} text-left`, children: errorMessage }))] }));
8824
8924
  const commonProps = {
8825
8925
  id: selectId,
8826
8926
  name,
8927
+ // Etiquetado accesible. Va en la RAÍZ y no en el disparador a propósito: el nombre del
8928
+ // disparador lo compone react-aria a partir de las props de la raíz (`useSelect` →
8929
+ // `useLabel`), y puesto en `Select.Trigger` se quedaría como un `aria-label` suelto que
8930
+ // compite con el `aria-labelledby` que esa misma pieza ya emite. Comprobado en el ÁRBOL DE
8931
+ // ACCESIBILIDAD, no reconstruido a mano — ver `Select.test.tsx` y el reporte del lane.
8932
+ "aria-label": ariaLabel,
8933
+ "aria-labelledby": ariaLabelledBy,
8934
+ "aria-describedby": ariaDescribedBy,
8935
+ "aria-details": ariaDetails,
8827
8936
  placeholder,
8828
8937
  disabledKeys,
8829
8938
  isInvalid,
@@ -8833,15 +8942,18 @@ fullWidth = true, isOpen, defaultOpen, onOpenChange, className, classNames, }) =
8833
8942
  defaultOpen,
8834
8943
  onOpenChange,
8835
8944
  fullWidth,
8836
- className: [
8837
- FIELD_STACK_CLASS$1[size],
8838
- // `w-full` y `min-w-40` son las dos ramas EXCLUYENTES de `fullWidth` en el `tv` de v2
8839
- // (ver `ROOT_MIN_WIDTH_CLASS`).
8840
- fullWidth ? "w-full" : ROOT_MIN_WIDTH_CLASS,
8841
- className,
8842
- ]
8843
- .filter(Boolean)
8844
- .join(" "),
8945
+ // `cn`, no una concatenación: el `className` del consumidor tiene que GANAR a las clases
8946
+ // de la fachada, y con una cadena suelta llegan las dos al DOM y decide el orden en que
8947
+ // Tailwind 4 las emite. Con `fullWidth` por defecto a `true`, eso dejaba `w-full` ganando
8948
+ // a cualquier ancho que pasara la app: medido, un `className="w-48"` en una fila flex de
8949
+ // 592px daba 290px (lo mismo que sin clase) y un `w-56` en un bloque de 1287px daba 1287.
8950
+ // v2 fusionaba: `useSelect` metía el `className` en `slots.base({ class: baseStyles })`
8951
+ // (`@heroui/select@2.4.22`, `dist/chunk-ONGPOZUT.mjs:171,202`) y el `tv` de v2 lleva
8952
+ // `twMerge: true` por defecto (`@heroui/theme@2.4.15`, `dist/utils/tv.js:56`).
8953
+ className: react.cn(FIELD_STACK_CLASS$1[size],
8954
+ // `w-full` y `min-w-40` son las dos ramas EXCLUYENTES de `fullWidth` en el `tv` de v2
8955
+ // (ver `ROOT_MIN_WIDTH_CLASS`).
8956
+ fullWidth ? "w-full" : ROOT_MIN_WIDTH_CLASS, className),
8845
8957
  };
8846
8958
  if (selectionMode === "multiple") {
8847
8959
  return (jsxRuntime.jsx(react.Select, { ...commonProps, selectionMode: "multiple", value: toArray(selectedKeys), defaultValue: toArray(defaultSelectedKeys), onChange: (keys) => onSelectionChange?.(new Set(keys)), children: inner }));
@@ -9132,9 +9244,13 @@ const FIELD_STACK_CLASS = "flex flex-col gap-1.5";
9132
9244
  // borde transparente de 2px NO es inocuo, porque con `box-sizing: border-box` come 2px por
9133
9245
  // lado y empuja el texto (medido: la sangría salía 14px en vez de los 12px de la referencia).
9134
9246
  // 2. `underlined` usa `!px-1` (4px), no el padding del tamaño, y su sombra es
9135
- // `0 1px 0 0 rgba(0,0,0,.05)` en vez de `shadow-sm`. Van con `!` porque estas clases NO pasan
9136
- // por `cn`: se concatenan a pelo, así que entre `px-3` y `px-1` no decide el orden del array
9137
- // sino el orden del CSS generado (medido: ganaba `px-3`).
9247
+ // `0 1px 0 0 rgba(0,0,0,.05)` en vez de `shadow-sm`. **El `!` sigue siendo imprescindible aunque
9248
+ // estas clases ya pasen por `cn`**: tailwind-merge 3 trata la forma con `!` como un grupo
9249
+ // APARTE de la forma sin `!`, así que NO resuelve `px-3` contra `px-1!` — medido con el `cn`
9250
+ // real del paquete instalado, `cn("px-3","px-1!")` → `"px-3 px-1!"`, las dos en el DOM. Quien
9251
+ // decide es entonces la `!important`, no el orden del array ni el del CSS generado (sin el `!`
9252
+ // ganaba `px-3`; medido computado con el `!`: `padding-inline: 4px`). No lo quites al ver que la
9253
+ // cadena ya se fusiona.
9138
9254
  // 3. `isInvalid` en v2 teñía TODO de `danger` —borde, label y texto/placeholder—, no solo el
9139
9255
  // borde; y en `flat` cambiaba el fondo a `danger-50`.
9140
9256
  // ⚠️ `bordered` NO lleva fondo, a propósito (TECH-2459 · F5, hallazgo de S2 + queja directa del
@@ -9159,6 +9275,24 @@ const VARIANT_CLASS = {
9159
9275
  // placeholder en el gris `default-500` (`113,113,122`) pese a ser un campo inválido.
9160
9276
  const INVALID_LABEL_CLASS = "text-danger";
9161
9277
  const INVALID_TEXT_CLASS = "text-danger";
9278
+ // ── El `className` del consumidor: FUSIONADO y en la RAÍZ ────────────────────────────────────
9279
+ //
9280
+ // Mismo defecto doble que en `Input.tsx` (ver ahí el razonamiento completo y las medidas), porque
9281
+ // en v2 este componente ERA el mismo: el `Textarea` de v2 es el `Input` con `isMultiline`, y pasa
9282
+ // por el mismo `useInput` (`@heroui/input@2.4.22`, `dist/chunk-F2LJ3HQE.mjs:68,166`), que mete el
9283
+ // `className` del consumidor en `slots.base({ class: baseStyles })` — fusionado con tailwind-merge
9284
+ // y en la RAÍZ, no en el elemento del campo.
9285
+ //
9286
+ // Aquí el `className` se emitía sobre el `<textarea>` y, además, concatenado: `w-full` y el ancho
9287
+ // del consumidor llegaban los dos al DOM y ganaba `w-full`. Medido: un `Textarea` con
9288
+ // `className="w-48"` en una fila flex de 592px medía 290px, lo mismo que su hermano sin clase.
9289
+ //
9290
+ // El `w-full` del `<textarea>` pasa a ser incondicional porque también lo era en v2: su slot
9291
+ // `input` es `["w-full font-normal bg-transparent …"]`, sin variante que lo condicione
9292
+ // (`@heroui/theme@2.4.15`, `dist/components/input.js`). `fullWidth` decide solo el ancho de la raíz.
9293
+ //
9294
+ // El canal para tocar la caja del campo sigue siendo `classNames.input`/`classNames.inputWrapper`,
9295
+ // igual que en v2.
9162
9296
  /**
9163
9297
  * Textarea genérico reutilizable basado en HeroUI v3 (TextField + TextArea) siguiendo las reglas
9164
9298
  * de diseño BeweOS (TECH-2459 · F3 · G1-forms). La API pública v2 (label, description,
@@ -9190,32 +9324,13 @@ fullWidth = true, value, defaultValue, onChange, onValueChange, className, class
9190
9324
  onChange?.(event);
9191
9325
  onValueChange?.(event.target.value);
9192
9326
  };
9193
- return (jsxRuntime.jsxs(react.TextField, { id: textareaId, name: name, isInvalid: isInvalid, isRequired: isRequired, isDisabled: isDisabled, isReadOnly: isReadOnly, className: [FIELD_STACK_CLASS, fullWidth ? "w-full" : ""]
9194
- .filter(Boolean)
9195
- .join(" "), children: [label && (jsxRuntime.jsx(react.Label, { className: [
9196
- LABEL_CLASS,
9197
- isInvalid ? INVALID_LABEL_CLASS : "",
9198
- classNames?.label,
9199
- ]
9200
- .filter(Boolean)
9201
- .join(" "), children: label })), jsxRuntime.jsx(react.TextArea, { ...rest, ref: ref, placeholder: placeholder, value: value, defaultValue: defaultValue, onChange: handleChange, className: [
9202
- SIZE_CLASS[size],
9203
- RESIZE_CLASS,
9204
- RADIUS_CLASS$3[radius],
9205
- VARIANT_CLASS[variant],
9206
- isInvalid ? INVALID_TEXT_CLASS : "",
9207
- FIELD_FOCUS_CLASS,
9208
- FIELD_SHADOW_CLASS,
9209
- fullWidth ? "w-full" : "",
9210
- classNames?.input ?? classNames?.inputWrapper,
9211
- className,
9212
- ]
9213
- .filter(Boolean)
9214
- .join(" ") }), description && !isInvalid && (jsxRuntime.jsx(react.Description, { className: [HELPER_CLASS, "text-left", classNames?.description]
9215
- .filter(Boolean)
9216
- .join(" "), children: description })), isInvalid && errorMessage && (jsxRuntime.jsx(react.FieldError, { className: [HELPER_CLASS, "text-left", classNames?.errorMessage]
9217
- .filter(Boolean)
9218
- .join(" "), children: errorMessage }))] }));
9327
+ return (jsxRuntime.jsxs(react.TextField, { id: textareaId, name: name, isInvalid: isInvalid, isRequired: isRequired, isDisabled: isDisabled, isReadOnly: isReadOnly, className: react.cn(FIELD_STACK_CLASS, fullWidth ? "w-full" : "",
9328
+ // El `className` del consumidor, en la raíz y en último lugar: el slot `base` de
9329
+ // v2 y la posición que le deja ganar en tailwind-merge (ver bloque de arriba).
9330
+ 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: ref, placeholder: placeholder, value: value, defaultValue: defaultValue, onChange: handleChange, className: react.cn(SIZE_CLASS[size], RESIZE_CLASS, RADIUS_CLASS$3[radius], VARIANT_CLASS[variant], isInvalid ? INVALID_TEXT_CLASS : "", FIELD_FOCUS_CLASS, FIELD_SHADOW_CLASS,
9331
+ // Incondicional, como el slot `input` de v2: el campo llena siempre su raíz,
9332
+ // y es la raíz la que responde a `fullWidth` y al ancho del consumidor.
9333
+ "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 }))] }));
9219
9334
  });
9220
9335
  Textarea.displayName = "Textarea";
9221
9336