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

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 (132) hide show
  1. package/dist/assets/css/styles.css +23 -1
  2. package/dist/index.cjs.js +2757 -481
  3. package/dist/index.esm.js +2789 -498
  4. package/dist/types/__smoke__/v2-api-surface.d.ts +5 -2
  5. package/dist/types/__smoke__/v2-api-surface.d.ts.map +1 -1
  6. package/dist/types/compat/heroui-v2-shims.d.ts +212 -34
  7. package/dist/types/compat/heroui-v2-shims.d.ts.map +1 -1
  8. package/dist/types/components/autocomplete/AutoComplete.d.ts.map +1 -1
  9. package/dist/types/components/autocomplete/AutoComplete.types.d.ts +36 -2
  10. package/dist/types/components/autocomplete/AutoComplete.types.d.ts.map +1 -1
  11. package/dist/types/components/autocomplete/index.d.ts +1 -1
  12. package/dist/types/components/autocomplete/index.d.ts.map +1 -1
  13. package/dist/types/components/button/Button.d.ts +3 -0
  14. package/dist/types/components/button/Button.d.ts.map +1 -1
  15. package/dist/types/components/button/Button.types.d.ts +24 -1
  16. package/dist/types/components/button/Button.types.d.ts.map +1 -1
  17. package/dist/types/components/button/_internal/press-event.d.ts +16 -0
  18. package/dist/types/components/button/_internal/press-event.d.ts.map +1 -0
  19. package/dist/types/components/button/index.d.ts +1 -1
  20. package/dist/types/components/button/index.d.ts.map +1 -1
  21. package/dist/types/components/card/Card.d.ts +13 -1
  22. package/dist/types/components/card/Card.d.ts.map +1 -1
  23. package/dist/types/components/card/Card.types.d.ts +53 -1
  24. package/dist/types/components/card/Card.types.d.ts.map +1 -1
  25. package/dist/types/components/card/index.d.ts +1 -1
  26. package/dist/types/components/card/index.d.ts.map +1 -1
  27. package/dist/types/components/chip/Chip.d.ts +1 -1
  28. package/dist/types/components/chip/Chip.d.ts.map +1 -1
  29. package/dist/types/components/chip/Chip.types.d.ts +28 -6
  30. package/dist/types/components/chip/Chip.types.d.ts.map +1 -1
  31. package/dist/types/components/datepicker/DatePicker.d.ts +1 -1
  32. package/dist/types/components/datepicker/DatePicker.d.ts.map +1 -1
  33. package/dist/types/components/datepicker/DatePicker.types.d.ts +89 -1
  34. package/dist/types/components/datepicker/DatePicker.types.d.ts.map +1 -1
  35. package/dist/types/components/datepicker/calendar-slots.d.ts +17 -0
  36. package/dist/types/components/datepicker/calendar-slots.d.ts.map +1 -0
  37. package/dist/types/components/datepicker/index.d.ts +1 -1
  38. package/dist/types/components/datepicker/index.d.ts.map +1 -1
  39. package/dist/types/components/daterangepicker/DateRangePicker.d.ts +1 -1
  40. package/dist/types/components/daterangepicker/DateRangePicker.d.ts.map +1 -1
  41. package/dist/types/components/daterangepicker/DateRangePicker.types.d.ts +43 -1
  42. package/dist/types/components/daterangepicker/DateRangePicker.types.d.ts.map +1 -1
  43. package/dist/types/components/daterangepicker/index.d.ts +1 -1
  44. package/dist/types/components/daterangepicker/index.d.ts.map +1 -1
  45. package/dist/types/components/drawer/Drawer.d.ts +58 -0
  46. package/dist/types/components/drawer/Drawer.d.ts.map +1 -0
  47. package/dist/types/components/drawer/Drawer.types.d.ts +75 -0
  48. package/dist/types/components/drawer/Drawer.types.d.ts.map +1 -0
  49. package/dist/types/components/drawer/DrawerContext.d.ts +15 -0
  50. package/dist/types/components/drawer/DrawerContext.d.ts.map +1 -0
  51. package/dist/types/components/drawer/_internal/DrawerBody.d.ts +7 -0
  52. package/dist/types/components/drawer/_internal/DrawerBody.d.ts.map +1 -0
  53. package/dist/types/components/drawer/_internal/DrawerContent.d.ts +14 -0
  54. package/dist/types/components/drawer/_internal/DrawerContent.d.ts.map +1 -0
  55. package/dist/types/components/drawer/_internal/DrawerFooter.d.ts +13 -0
  56. package/dist/types/components/drawer/_internal/DrawerFooter.d.ts.map +1 -0
  57. package/dist/types/components/drawer/_internal/DrawerHeader.d.ts +20 -0
  58. package/dist/types/components/drawer/_internal/DrawerHeader.d.ts.map +1 -0
  59. package/dist/types/components/drawer/_internal/index.d.ts +5 -0
  60. package/dist/types/components/drawer/_internal/index.d.ts.map +1 -0
  61. package/dist/types/components/drawer/index.d.ts +4 -0
  62. package/dist/types/components/drawer/index.d.ts.map +1 -0
  63. package/dist/types/components/dropdown/Dropdown.d.ts +4 -2
  64. package/dist/types/components/dropdown/Dropdown.d.ts.map +1 -1
  65. package/dist/types/components/dropdown/Dropdown.types.d.ts +61 -5
  66. package/dist/types/components/dropdown/Dropdown.types.d.ts.map +1 -1
  67. package/dist/types/components/dropdown/_internal/renderTrigger.d.ts +47 -0
  68. package/dist/types/components/dropdown/_internal/renderTrigger.d.ts.map +1 -0
  69. package/dist/types/components/dropdown/index.d.ts +1 -1
  70. package/dist/types/components/dropdown/index.d.ts.map +1 -1
  71. package/dist/types/components/input/Input.d.ts +4 -0
  72. package/dist/types/components/input/Input.d.ts.map +1 -1
  73. package/dist/types/components/input/Input.types.d.ts +37 -1
  74. package/dist/types/components/input/Input.types.d.ts.map +1 -1
  75. package/dist/types/components/modal/Modal.d.ts +4 -3
  76. package/dist/types/components/modal/Modal.d.ts.map +1 -1
  77. package/dist/types/components/modal/Modal.types.d.ts +59 -3
  78. package/dist/types/components/modal/Modal.types.d.ts.map +1 -1
  79. package/dist/types/components/modal/ModalFacadeContext.d.ts +27 -0
  80. package/dist/types/components/modal/ModalFacadeContext.d.ts.map +1 -0
  81. package/dist/types/components/modal/_internal/ModalBody.d.ts +1 -1
  82. package/dist/types/components/modal/_internal/ModalBody.d.ts.map +1 -1
  83. package/dist/types/components/modal/_internal/ModalContent.d.ts +10 -5
  84. package/dist/types/components/modal/_internal/ModalContent.d.ts.map +1 -1
  85. package/dist/types/components/modal/_internal/ModalFooter.d.ts +1 -1
  86. package/dist/types/components/modal/_internal/ModalFooter.d.ts.map +1 -1
  87. package/dist/types/components/modal/_internal/ModalHeader.d.ts +4 -2
  88. package/dist/types/components/modal/_internal/ModalHeader.d.ts.map +1 -1
  89. package/dist/types/components/pagination/Pagination.d.ts +10 -0
  90. package/dist/types/components/pagination/Pagination.d.ts.map +1 -1
  91. package/dist/types/components/pagination/Pagination.types.d.ts +21 -0
  92. package/dist/types/components/pagination/Pagination.types.d.ts.map +1 -1
  93. package/dist/types/components/select/Select.d.ts.map +1 -1
  94. package/dist/types/components/select/Select.types.d.ts +64 -0
  95. package/dist/types/components/select/Select.types.d.ts.map +1 -1
  96. package/dist/types/components/select/_internal/v2-collection.d.ts +64 -0
  97. package/dist/types/components/select/_internal/v2-collection.d.ts.map +1 -0
  98. package/dist/types/components/select/index.d.ts +1 -1
  99. package/dist/types/components/select/index.d.ts.map +1 -1
  100. package/dist/types/components/table/AuraTable.types.d.ts +5 -3
  101. package/dist/types/components/table/AuraTable.types.d.ts.map +1 -1
  102. package/dist/types/components/table/Table.d.ts +15 -5
  103. package/dist/types/components/table/Table.d.ts.map +1 -1
  104. package/dist/types/components/table/TableContent.d.ts +32 -0
  105. package/dist/types/components/table/TableContent.d.ts.map +1 -0
  106. package/dist/types/components/table/_internal/v2-compat.d.ts +94 -0
  107. package/dist/types/components/table/_internal/v2-compat.d.ts.map +1 -0
  108. package/dist/types/components/tabs/Tabs.d.ts +5 -4
  109. package/dist/types/components/tabs/Tabs.d.ts.map +1 -1
  110. package/dist/types/components/tabs/Tabs.types.d.ts +90 -13
  111. package/dist/types/components/tabs/Tabs.types.d.ts.map +1 -1
  112. package/dist/types/components/textarea/Textarea.d.ts.map +1 -1
  113. package/dist/types/components/textarea/Textarea.types.d.ts +13 -0
  114. package/dist/types/components/textarea/Textarea.types.d.ts.map +1 -1
  115. package/dist/types/components/textarea/useTextareaAutosize.d.ts +13 -0
  116. package/dist/types/components/textarea/useTextareaAutosize.d.ts.map +1 -0
  117. package/dist/types/components/time-input/TimeInput.d.ts +12 -8
  118. package/dist/types/components/time-input/TimeInput.d.ts.map +1 -1
  119. package/dist/types/components/time-input/TimeInput.types.d.ts +29 -3
  120. package/dist/types/components/time-input/TimeInput.types.d.ts.map +1 -1
  121. package/dist/types/components/time-input/index.d.ts +2 -2
  122. package/dist/types/components/time-input/index.d.ts.map +1 -1
  123. package/dist/types/components/tooltip/Tooltip.d.ts.map +1 -1
  124. package/dist/types/components/tooltip/Tooltip.types.d.ts +31 -1
  125. package/dist/types/components/tooltip/Tooltip.types.d.ts.map +1 -1
  126. package/dist/types/components/tooltip/_internal/renderTrigger.d.ts +3 -0
  127. package/dist/types/components/tooltip/_internal/renderTrigger.d.ts.map +1 -0
  128. package/dist/types/components/tooltip/index.d.ts +1 -1
  129. package/dist/types/components/tooltip/index.d.ts.map +1 -1
  130. package/dist/types/index.d.ts +8 -7
  131. package/dist/types/index.d.ts.map +1 -1
  132. package/package.json +1 -1
package/dist/index.cjs.js CHANGED
@@ -19,47 +19,159 @@ const CardBody = react.Card.Content;
19
19
  // Breadcrumbs: el `Breadcrumbs` raíz sobrevive igual; su hijo suelto se renombra Y cambia grafía
20
20
  // (`BreadcrumbItem` v2 → `BreadcrumbsItem`/`Breadcrumbs.Item` v3, con "s").
21
21
  const BreadcrumbItem = react.Breadcrumbs.Item;
22
- // Autocomplete / Select: los ítems v2 (`AutocompleteItem`, `SelectItem`) y sus secciones
23
- // (`SelectSection`) no sobreviven planos en v3 — se resuelven con `ListBox.Item`/
24
- // `ListBox.Section`, la misma pieza que usan tanto el `ComboBox` (ver `AuraAutocomplete`) como el
25
- // `Select` compound.
26
- //
27
- // ⚠️ Limitación conocida (ver decisiones-aceptadas-f2.md, D-F2-2): v2 identificaba cada item por
28
- // el `key` de React; v3 usa `id` explícito. El código v2 existente (que solo pasa `key`/
29
- // `textValue`) sigue compilando y seleccionando bien porque react-aria-components cae al `key` de
30
- // React cuando no hay `id` en colecciones estáticas (verificado en Storybook) — pero si algún
31
- // consumidor lee la selección por otra vía, debe migrar sus items a `id` explícito.
32
22
  const AutocompleteItem = react.ListBoxItem;
33
23
  const SelectItem = react.ListBoxItem;
34
24
  const SelectSection = react.ListBoxSection;
35
25
  // ---------------------------------------------------------------------------
36
26
  // Renombrados simples (el nombre v2 desaparece del todo, 1:1 con un nombre v3)
37
27
  // ---------------------------------------------------------------------------
38
- /** Progress → ProgressBar. */
39
- const Progress = react.ProgressBar;
28
+ // Progress: ver el bloque «Progress (v2) sobre ProgressBar (v3)» más abajo.
40
29
  /** Divider → Separator. El uso INTERNO de la librería ya migró a `Separator` directo
41
30
  * (`TagsFilter.tsx`); este shim es solo para las apps, que siguen importando `Divider`. */
42
31
  const Divider = react.Separator;
43
- // ---------------------------------------------------------------------------
44
- // Eliminados
45
- // ---------------------------------------------------------------------------
46
32
  /**
47
- * `HeroUIProvider` ya no existe en v3 (los componentes no necesitan Provider). Shim no-op: pasa
48
- * los `children` tal cual para que el `<HeroUIProvider>` (aliased `AuroraProvider`) que envuelve
49
- * la raíz de las 4 apps siga compilando y renderizando sin cambios.
33
+ * El relleno de v3 se pinta con `var(--progress-bar-fill)` (`progress-bar.css`), que cada variante
34
+ * de color fija en la capa `components`. v3 tiene 5 colores y v2 seis, y dos NO coinciden:
35
+ *
36
+ * | v2 | literal v2 (`indicator`) | v3 con el mismo nombre | aquí |
37
+ * |---|---|---|---|
38
+ * | `primary` (default) | `bg-primary` | — (no existe) | `accent`, que en este repo ES `--color-primary` (`theme-packs.generated.css`: `--accent: var(--color-primary)`) |
39
+ * | `default` | `bg-default-400` (gris medio) | `--default-foreground` (casi negro) | falso amigo: se repone el gris de v2 |
40
+ * | `secondary` | `bg-secondary` | — | el `--color-secondary` del pack |
41
+ *
42
+ * La variable se pisa con una utilidad (capa `utilities` > `components`), así que no depende del
43
+ * orden del CSS. `success`/`warning`/`danger` son el mismo nombre y el mismo token en las dos.
44
+ */
45
+ const PROGRESS_FILL_OVERRIDE = {
46
+ default: "[--progress-bar-fill:var(--color-default-400)]",
47
+ secondary: "[--progress-bar-fill:var(--color-secondary)]",
48
+ };
49
+ const PROGRESS_V3_COLOR = {
50
+ default: undefined,
51
+ primary: "accent",
52
+ secondary: undefined,
53
+ success: "success",
54
+ warning: "warning",
55
+ danger: "danger",
56
+ accent: "accent",
57
+ };
58
+ /** Los hijos que el consumidor ya trae, aplanando Fragments (`Children.toArray` NO los aplana). */
59
+ function flattenChildren(children) {
60
+ const out = [];
61
+ for (const child of $8WNJc$react.Children.toArray(children)) {
62
+ if (!$8WNJc$react.isValidElement(child)) {
63
+ continue;
64
+ }
65
+ if (child.type === $8WNJc$react.Fragment) {
66
+ out.push(...flattenChildren(child.props.children));
67
+ }
68
+ else {
69
+ out.push(child);
70
+ }
71
+ }
72
+ return out;
73
+ }
74
+ /**
75
+ * `Progress` de v2 (TECH-2459, hallazgo beweossmbs 23-sep: «es un alias del ProgressBar crudo y
76
+ * SIN HIJOS NO PINTA»).
77
+ *
78
+ * EL DEFECTO. Hasta aquí `Progress` era `ProgressBar` a secas, y el `ProgressBar` de v3 es un
79
+ * compound: la raíz solo publica el estado, la barra la pintan `ProgressBar.Track` +
80
+ * `ProgressBar.Fill`, y la etiqueta y el valor, `Label` + `ProgressBar.Output`. El JSX v2 de las
81
+ * apps (`<Progress value={40} color="primary" showValueLabel />`, 10 usos en beweossmbs) no trae
82
+ * ninguna de esas piezas, así que salía un `<div role="progressbar">` VACÍO: ni pista, ni relleno,
83
+ * ni texto. Además `color="primary"` no es un color de v3 y `showValueLabel`/`label`/`classNames`
84
+ * se descartaban.
85
+ *
86
+ * LO QUE HACE. Acepta la API v2 y monta por dentro la anatomía de v3. Es **idempotente por
87
+ * identidad de `child.type`**: si la app ya pasa `ProgressBar.Track` (o `Progress.Track`),
88
+ * `ProgressBar.Output` o `Label`, esa pieza no se añade otra vez (mismo criterio que la fachada
89
+ * `Tabs` con su indicador). Children como función se respetan tal cual.
90
+ *
91
+ * LO QUE NO COPIA DE v2, por §9.1 (verse como v3): las alturas de pista (v2 `h-1`/`h-3`/`h-5`,
92
+ * v3 `h-1`/`h-2`/`h-3`), el radio completo y el fondo `default-300/50` de la pista, y el tamaño de
93
+ * letra por `size` de la etiqueta. `size` elige el tamaño de v3 del mismo nombre.
94
+ */
95
+ function ProgressFacade({ label, showValueLabel = false, size = "md", color = "primary", classNames, className, children, ...props }) {
96
+ if (typeof children === "function") {
97
+ return (jsxRuntime.jsx(react.ProgressBar, { ...props, size: size, color: PROGRESS_V3_COLOR[color], className: react.cn(PROGRESS_FILL_OVERRIDE[color], classNames?.base, className), children: children }));
98
+ }
99
+ const own = flattenChildren(children);
100
+ const hasTrack = own.some((c) => c.type === react.ProgressBar.Track || c.type === react.ProgressBar.Fill);
101
+ const hasOutput = own.some((c) => c.type === react.ProgressBar.Output);
102
+ const hasLabel = own.some((c) => c.type === react.Label);
103
+ return (jsxRuntime.jsxs(react.ProgressBar, { ...props, size: size, color: PROGRESS_V3_COLOR[color],
104
+ // v2: `cn(classNames?.base, className)` en la raíz (`use-progress.mjs`). La variable del
105
+ // color va primero para que un `[--progress-bar-fill:…]` del consumidor la sustituya.
106
+ className: react.cn(PROGRESS_FILL_OVERRIDE[color], classNames?.base, className), children: [label != null && label !== false && !hasLabel && (jsxRuntime.jsx(react.Label, { className: classNames?.label, children: label })), showValueLabel && !hasOutput && (jsxRuntime.jsx(react.ProgressBar.Output, { className: classNames?.value })), children, !hasTrack && (jsxRuntime.jsx(react.ProgressBar.Track, { className: classNames?.track, children: jsxRuntime.jsx(react.ProgressBar.Fill, { className: classNames?.indicator }) }))] }));
107
+ }
108
+ /**
109
+ * `Progress` de v2 con la anatomía de v3 dentro, y con los estáticos del compound colgados
110
+ * (`Progress.Track`, `Progress.Fill`, `Progress.Output`, `Progress.Root`) para que el marcado v3
111
+ * siga compilando: son los MISMOS objetos que `ProgressBar.*`, así que la detección por identidad
112
+ * los reconoce vengan por un nombre o por otro.
113
+ */
114
+ const Progress = Object.assign(ProgressFacade, {
115
+ Root: react.ProgressBar.Root,
116
+ Output: react.ProgressBar.Output,
117
+ Track: react.ProgressBar.Track,
118
+ Fill: react.ProgressBar.Fill,
119
+ });
120
+ /**
121
+ * `HeroUIProvider` ya no existe en v3 (los componentes no necesitan Provider para pintarse). Pero
122
+ * el de v2 hacía dos cosas que react-aria sí necesita y que las apps no pueden montar por su
123
+ * cuenta (todo pasa por aurora-ui): `locale` → `I18nProvider` y `navigate`/`useHref` →
124
+ * `RouterProvider`. Este shim las repone, cada una SOLO cuando se pasa; sin props pasa los
125
+ * `children` tal cual, como el no-op que era. El resto de props v2 se aceptan y se ignoran (ver
126
+ * `HeroUIProviderProps`). Orden de anidado idéntico al de v2: `I18nProvider` por fuera,
127
+ * `RouterProvider` por dentro.
50
128
  */
51
- function HeroUIProvider({ children }) {
52
- return jsxRuntime.jsx(jsxRuntime.Fragment, { children: children });
129
+ function HeroUIProvider({ children, locale, navigate, useHref, }) {
130
+ let contents = children;
131
+ if (navigate) {
132
+ contents = (jsxRuntime.jsx(react.RouterProvider, { navigate: navigate, useHref: useHref, children: contents }));
133
+ }
134
+ if (locale) {
135
+ contents = jsxRuntime.jsx(react.I18nProvider, { locale: locale, children: contents });
136
+ }
137
+ return jsxRuntime.jsx(jsxRuntime.Fragment, { children: contents });
53
138
  }
139
+ /*
140
+ * Literales del `tv` de v2, escritos enteros para que Tailwind 4 los genere (solo emite lo que
141
+ * aparece en el fuente). v2 decía `rounded-small`/`rounded-large` —clases del plugin `heroui()`,
142
+ * muertas sin él y que `cn` BORRARÍA— con los valores de su tema: small 8px, large 14px (los
143
+ * mismos que usan `RADIUS_CLASS` de `Select`/`Input` para `sm`/`lg`). `md` era `rounded-md` a secas
144
+ * en el `tv` de v2 (el de Tailwind, 6px), no el `medium` del tema: se copia tal cual.
145
+ */
146
+ const IMAGE_RADIUS_CLASS = {
147
+ none: "rounded-none",
148
+ sm: "rounded-[8px]",
149
+ md: "rounded-md",
150
+ lg: "rounded-[14px]",
151
+ full: "rounded-full",
152
+ };
54
153
  /**
55
154
  * `Image` se elimina en v3 (usar `<img>` nativo, mismo criterio aplicado en
56
- * `SocialMediaPreview`/`WhatsAppPreview`). Shim: mismo nombre, props de un `<img>` nativo — NO
57
- * reproduce `radius`/`isBlurred`/`isZoomed`/`fallbackSrc` de v2 (sin equivalente razonable sin
58
- * reescribir una librería de imágenes completa); limitación documentada.
155
+ * `SocialMediaPreview`/`WhatsAppPreview`). Shim: un `<img>` nativo con la API de v2 que las apps
156
+ * escriben (TECH-2459, hallazgo beweossmbs 23-sep: 12 `<Image>` en la app; `radius` en 3,
157
+ * `classNames` en 3, `removeWrapper` en 2; 0 usos de `isZoomed`/`isBlurred`/`fallbackSrc`/`shadow`).
158
+ *
159
+ * - `radius`: con el default de v2 (`lg`). Sin él, las 9 imágenes que NO pasan `radius` salían con
160
+ * esquinas rectas en vez de las redondeadas de v2. Va primero en el `cn`: la clase de la app gana.
161
+ * - `className` + `classNames.img`: en v2 las dos iban al `<img>` (`use-image.mjs`:
162
+ * `cn(className, classNames?.img)`), y así aquí.
163
+ * - `classNames.wrapper`/`zoomedWrapper`/`blurredImg` y `removeWrapper` se ACEPTAN y no pintan: este
164
+ * shim no monta envoltorio. En v2 el `<div>` existía para el skeleton, el zoom y el desenfoque
165
+ * (`image.mjs`), y le ponía `max-width: fit-content` en línea — que es exactamente lo que los dos
166
+ * `classNames.wrapper` medidos deshacen («El wrapper de HeroUI Image trae `max-w-fit`», con
167
+ * `w-full !max-w-full`). Sin envoltorio, el `<img>` con su `w-full` ya ocupa el ancho: el efecto
168
+ * buscado sale sin la pieza.
169
+ * - NO reproduce `isZoomed`/`isBlurred`/`fallbackSrc`/`shadow`/skeleton (0 usos medidos).
59
170
  */
60
- function Image$1(props) {
171
+ function Image$1({ radius = "lg", classNames, removeWrapper: _removeWrapper, className, ...props }) {
172
+ return (
61
173
  // biome-ignore lint/a11y/useAltText: passthrough genérico, `alt` lo exige ComponentProps<'img'>
62
- return jsxRuntime.jsx("img", { ...props });
174
+ jsxRuntime.jsx("img", { ...props, className: react.cn(IMAGE_RADIUS_CLASS[radius], className, classNames?.img) }));
63
175
  }
64
176
  // ---------------------------------------------------------------------------
65
177
  // Hooks
@@ -84,6 +196,42 @@ function useDisclosure(props) {
84
196
  };
85
197
  }
86
198
 
199
+ /**
200
+ * `click` nativo → un `PressEvent` con la forma del de react-aria.
201
+ *
202
+ * Lo usan las piezas de esta librería que NO pasan por un componente de react-aria y aun así
203
+ * exponen el `onPress` de v2: la `Card` pulsable (`<button>` nativo) y el `Button` con `href`
204
+ * (`<a>` nativo). No se importa `usePress` de react-aria porque no es `external` en
205
+ * `rollup.config.mjs`: se metería una segunda copia dentro del bundle.
206
+ *
207
+ * Un `click` nativo ya cubre ratón, táctil y teclado (Enter/Espacio en un botón, Enter en un
208
+ * enlace). `detail === 0` es un click sintético —teclado o `.click()`—, y como react-aria, se
209
+ * sitúa entonces el punto en el centro del elemento (lo necesita la onda expansiva).
210
+ */
211
+ function pressEventFromClick(event) {
212
+ const target = event.currentTarget;
213
+ const rect = target.getBoundingClientRect();
214
+ const fromKeyboard = event.detail === 0;
215
+ const native = event.nativeEvent;
216
+ const pointerType = fromKeyboard
217
+ ? "keyboard"
218
+ : native.pointerType === "touch" || native.pointerType === "pen"
219
+ ? native.pointerType
220
+ : "mouse";
221
+ return {
222
+ type: "press",
223
+ pointerType,
224
+ target,
225
+ shiftKey: event.shiftKey,
226
+ ctrlKey: event.ctrlKey,
227
+ metaKey: event.metaKey,
228
+ altKey: event.altKey,
229
+ x: fromKeyboard ? rect.width / 2 : event.clientX - rect.left,
230
+ y: fromKeyboard ? rect.height / 2 : event.clientY - rect.top,
231
+ continuePropagation: () => undefined,
232
+ };
233
+ }
234
+
87
235
  /** `clamp(0.01 * size, 0.2, size > 100 ? 0.75 : 0.5)` de v2, en ms. */
88
236
  function duracionDeOnda(size) {
89
237
  const maximo = size > 100 ? 750 : 500;
@@ -376,6 +524,77 @@ const SHAPE_CLASS = {
376
524
  outline: "border-2",
377
525
  ghost: "",
378
526
  };
527
+ // ── Props DOM que el `Button` de react-aria-components descarta ────────────────────────────
528
+ //
529
+ // `title` (tooltip nativo, 3 usos en beweossmbs: los «Cancelar importación»), `tabIndex` (2),
530
+ // `aria-busy`/`aria-live`… no llegaban al `<button>`: el `Button` de react-aria-components pasa
531
+ // solo lo que deja su `filterDOMProps({ global: true })` más lo que genera `useButton` (ver
532
+ // `ButtonDOMProps` en `Button.types.ts`). v2 los pasaba todos. Se reponen con la prop `render` de
533
+ // react-aria (el mismo `<button>`, con el `ref` ya conectado en `domProps`). Van DESPUÉS de lo
534
+ // que genera react-aria, como en v2 (`mergeProps(ariaButtonProps, …, filterDOMProps(otherProps))`
535
+ // → gana el consumidor): medido, react-aria escribe `tabIndex="0"` en el botón, y el
536
+ // `tabIndex={-1}` que pasa `client-table-row` para sacarlo del orden de tabulación se perdía.
537
+ const DOM_ATTRS_DESCARTADOS = new Set([
538
+ "title",
539
+ "tabIndex",
540
+ "role",
541
+ "accessKey",
542
+ "draggable",
543
+ "spellCheck",
544
+ ]);
545
+ function atributosDescartados(props) {
546
+ const out = {};
547
+ for (const [key, value] of Object.entries(props)) {
548
+ if (value !== undefined &&
549
+ (DOM_ATTRS_DESCARTADOS.has(key) || key.startsWith("aria-"))) {
550
+ out[key] = value;
551
+ }
552
+ }
553
+ return out;
554
+ }
555
+ // Props de react-aria que NO son atributos DOM: en el camino de enlace (un `<a>` nativo, que no
556
+ // pasa por react-aria) no se reenvían, o React avisaría de atributos desconocidos.
557
+ const PROPS_SOLO_REACT_ARIA = new Set([
558
+ "onPressStart",
559
+ "onPressEnd",
560
+ "onPressChange",
561
+ "onPressUp",
562
+ "onHoverStart",
563
+ "onHoverEnd",
564
+ "onHoverChange",
565
+ "onFocusChange",
566
+ "excludeFromTabOrder",
567
+ "preventFocusOnPress",
568
+ "slot",
569
+ "fullWidth",
570
+ "render",
571
+ "ref",
572
+ "type",
573
+ "form",
574
+ "formAction",
575
+ "formEncType",
576
+ "formMethod",
577
+ "formNoValidate",
578
+ "formTarget",
579
+ "name",
580
+ "value",
581
+ ]);
582
+ function propsDeEnlace(props) {
583
+ const out = {};
584
+ for (const [key, value] of Object.entries(props)) {
585
+ if (value === undefined || PROPS_SOLO_REACT_ARIA.has(key)) {
586
+ continue;
587
+ }
588
+ if (DOM_ATTRS_DESCARTADOS.has(key) ||
589
+ key === "id" ||
590
+ key.startsWith("aria-") ||
591
+ key.startsWith("data-") ||
592
+ /^on[A-Z]/.test(key)) {
593
+ out[key] = value;
594
+ }
595
+ }
596
+ return out;
597
+ }
379
598
  /**
380
599
  * Botón genérico basado en HeroUI v3 siguiendo las reglas de diseño BeweOS (TECH-2459 · F3.0).
381
600
  *
@@ -391,8 +610,15 @@ const SHAPE_CLASS = {
391
610
  * - `disableRipple`: v3 no tiene ripple — reimplementado en `_internal/ripple.tsx`, así que la
392
611
  * prop vuelve a apagar algo.
393
612
  * - `ai`: aura de Linda para las acciones que hace la IA (ver `Button.types.ts`).
613
+ * - Props DOM (`title`, `tabIndex`, `aria-*`…): llegan al `<button>` aunque react-aria las
614
+ * descarte (ver `DOM_ATTRS_DESCARTADOS`). `disabled` nativo equivale a `isDisabled`.
615
+ * - `as="a"` / `href` (API v2): renderiza un `<a>` con las mismas clases y colores del botón.
394
616
  */
395
- const Button = ({ color = "primary", size = "md", variant = "solid", radius = "sm", startContent, endContent, isLoading = false, spinner, spinnerPlacement = "start", isIconOnly = false, isDisabled, ai, className, style, children, disableRipple = false, disableAnimation = false, onPress, ...rest }) => {
617
+ const Button = ({ color = "primary", size = "md", variant = "solid", radius = "sm", startContent, endContent, isLoading = false, spinner, spinnerPlacement = "start", isIconOnly = false, isDisabled, ai, className, style, children, disableRipple = false, disableAnimation = false, onPress, disabled, as, href, target, rel, download, hrefLang, referrerPolicy, ...rest }) => {
618
+ // v2 reenviaba `disabled` tal cual al `<button>` nativo, que así quedaba deshabilitado; en v3
619
+ // se descartaba y el botón seguía pulsable. Se trata como `isDisabled`.
620
+ const deshabilitado = Boolean(isDisabled || disabled || isLoading);
621
+ const esEnlace = href !== undefined || as === "a";
396
622
  const shape = SHAPE_BY_VARIANT$1[variant];
397
623
  const v3Variant = BASE_V3_VARIANT[shape];
398
624
  const spinnerNode = spinner ?? jsxRuntime.jsx(react.Spinner, { size: "sm", color: "current" });
@@ -430,20 +656,62 @@ const Button = ({ color = "primary", size = "md", variant = "solid", radius = "s
430
656
  * `.linda-aura > *` ya los alcanza.
431
657
  */
432
658
  const contenido = ai ? (jsxRuntime.jsx("span", { className: "linda-aura-content", children: children })) : (children);
433
- return (jsxRuntime.jsxs(react.Button, { ...rest, variant: v3Variant, size: size, isIconOnly: isIconOnly, isPending: isLoading, isDisabled: isDisabled || isLoading, onPress: alPulsar, className: [
434
- SHAPE_CLASS[shape],
435
- RADIUS_CLASS$a[radius],
436
- ai ? CLASE_AURA[ai] : "",
437
- // P-05: v2 apagaba transición + hundido con esta prop; v3 no tiene la prop, así
438
- // que el interruptor es una clase propia (`Button.css`).
439
- disableAnimation ? "button--no-animation" : "",
440
- // P-06b: hover atenuado de v2 en vez del cambio de fondo de v3 (ver
441
- // `HOVER_ATENUADO` arriba y `neutralize-v3.css`).
442
- HOVER_ATENUADO[shape] ? "button--hover-dim" : "",
443
- className,
444
- ]
445
- .filter(Boolean)
446
- .join(" "), style: { ...colorStyle$1(color, shape), ...style }, children: [isLoading && spinnerPlacement === "start" && spinnerNode, !isLoading && startContent, contenido, !isLoading && endContent, isLoading && spinnerPlacement === "end" && spinnerNode, ondas.length > 0 && jsxRuntime.jsx(CapaRipple, { ondas: ondas, onFin: quitar })] }));
659
+ const claseForma = [
660
+ SHAPE_CLASS[shape],
661
+ RADIUS_CLASS$a[radius],
662
+ ai ? CLASE_AURA[ai] : "",
663
+ // P-05: v2 apagaba transición + hundido con esta prop; v3 no tiene la prop, así
664
+ // que el interruptor es una clase propia (`Button.css`).
665
+ disableAnimation ? "button--no-animation" : "",
666
+ // P-06b: hover atenuado de v2 en vez del cambio de fondo de v3 (ver
667
+ // `HOVER_ATENUADO` arriba y `neutralize-v3.css`).
668
+ HOVER_ATENUADO[shape] ? "button--hover-dim" : "",
669
+ className,
670
+ ]
671
+ .filter(Boolean)
672
+ .join(" ");
673
+ const estilo = { ...colorStyle$1(color, shape), ...style };
674
+ const hijos = (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [isLoading && spinnerPlacement === "start" && spinnerNode, !isLoading && startContent, contenido, !isLoading && endContent, isLoading && spinnerPlacement === "end" && spinnerNode, ondas.length > 0 && jsxRuntime.jsx(CapaRipple, { ondas: ondas, onFin: quitar })] }));
675
+ /*
676
+ * `as="a"` / `href` — el botón-enlace de v2 (3 usos en beweossmbs: «abrir contacto en
677
+ * pestaña nueva», «ver publicación», el enlace de agenda de la demo de referidos).
678
+ *
679
+ * v3 no acepta `href` en `Button` (trampa 5 de la skill `auraui-components`) y el `href`
680
+ * caía sin más: el botón no navegaba. Se renderiza un `<a>` NATIVO con las clases del
681
+ * botón (`buttonVariants` de v3, las mismas que pone `Button`) + las de la fachada.
682
+ *
683
+ * Por qué no las otras dos vías: `render` del `Button` de react-aria devolviendo un `<a>`
684
+ * provoca un `console.warn` en desarrollo (espera el MISMO elemento); y el `Link` de v3
685
+ * añade la clase `.link`, y `.link.button { gap: 0 }` se come el hueco del icono. El `:hover`
686
+ * de `.button`/`.button--hover-dim` funciona igual en un `<a>` (el CSS escucha `:hover` además
687
+ * de `data-hovered`).
688
+ */
689
+ if (esEnlace) {
690
+ const alPulsarEnlace = (evento) => {
691
+ if (deshabilitado) {
692
+ evento.preventDefault();
693
+ return;
694
+ }
695
+ rest.onClick?.(evento);
696
+ alPulsar(pressEventFromClick(evento));
697
+ };
698
+ return (jsxRuntime.jsx("a", { ...propsDeEnlace(rest), "data-slot": "button", "data-disabled": deshabilitado || undefined, "aria-disabled": deshabilitado || undefined,
699
+ // Deshabilitado, sin `href`: un `<a>` no tiene `disabled` y seguiría navegando.
700
+ href: deshabilitado ? undefined : href, target: target, rel: rel, download: download, hrefLang: hrefLang, referrerPolicy: referrerPolicy, className: react.cn(react.buttonVariants({
701
+ variant: v3Variant,
702
+ size,
703
+ isIconOnly,
704
+ fullWidth: rest.fullWidth,
705
+ }), claseForma), style: estilo, onClick: alPulsarEnlace, children: hijos }));
706
+ }
707
+ const extras = atributosDescartados(rest);
708
+ const renderConExtras = Object.keys(extras).length > 0
709
+ ? (domProps) => {
710
+ const merged = { ...domProps, ...extras };
711
+ return (jsxRuntime.jsx("button", { ...merged }));
712
+ }
713
+ : undefined;
714
+ return (jsxRuntime.jsx(react.Button, { ...rest, ...(renderConExtras ? { render: renderConExtras } : {}), variant: v3Variant, size: size, isIconOnly: isIconOnly, isPending: isLoading, isDisabled: deshabilitado, onPress: alPulsar, className: claseForma, style: estilo, children: hijos }));
447
715
  };
448
716
 
449
717
  // TECH-2459 · F3.0 — Matriz de traducción v2 → v3 (mismo mecanismo que Button, documentado en
@@ -489,6 +757,21 @@ const SHAPE_BY_VARIANT = {
489
757
  flat: "soft",
490
758
  bordered: "outline",
491
759
  faded: "outline",
760
+ // `shadow` de v2 es `solid` + sombra teñida (ver `SHADOW_CLASS`).
761
+ shadow: "solid",
762
+ };
763
+ // `variant="shadow"` de v2, literal de `@heroui/theme@2.4.15` (`dist/utils/variants.js`,
764
+ // `colorVariants.shadow`): `shadow-lg shadow-<color>/40 bg-<color> text-<color>-foreground`
765
+ // (`default` usa `/50`). El relleno y el texto son los de `solid`; aquí solo se añade la sombra.
766
+ // Clases literales (no plantillas) para que Tailwind las genere al escanear `src/`. Los 5 colores
767
+ // fijos de Aurora (red/purple/…) no existían en v2 con `shadow`: reciben la sombra neutra.
768
+ const SHADOW_CLASS$1 = {
769
+ default: "shadow-lg shadow-default/50",
770
+ primary: "shadow-lg shadow-primary/40",
771
+ secondary: "shadow-lg shadow-secondary/40",
772
+ success: "shadow-lg shadow-success/40",
773
+ warning: "shadow-lg shadow-warning/40",
774
+ danger: "shadow-lg shadow-danger/40",
492
775
  };
493
776
  // TECH-2459 · F5 · P-08c — Tamaños de v2 (`chip.ts`): la altura era FIJA y el texto más grande que
494
777
  // en v3, y el sangrado horizontal salía de dos slots (`base` + `content`):
@@ -726,7 +1009,7 @@ function colorStyle(color, shape) {
726
1009
  * y el hueco que queda tras el botón de cierre sean distintos, como en v2 (ver `SIZE_CLASS` y
727
1010
  * `CONTENT_PADDING`).
728
1011
  */
729
- const Chip = ({ color = "default", variant = "solid", size = "md", radius = "full", startContent, endContent, onClose, isDisabled = false, className, classNames, children, id, }) => {
1012
+ const Chip = ({ color = "default", variant = "solid", size = "md", radius = "full", startContent, endContent, onClose, isDisabled = false, className, classNames, children, as, style: consumerStyle, ...domProps }) => {
730
1013
  const isStandard = STANDARD_COLORS.has(color);
731
1014
  const shape = SHAPE_BY_VARIANT[variant];
732
1015
  // Literal de v2 (`@heroui/chip@2.2.18`, `use-chip.js:63`):
@@ -750,12 +1033,27 @@ const Chip = ({ color = "default", variant = "solid", size = "md", radius = "ful
750
1033
  const flatDarkFgClass = isStandard && shape === "soft"
751
1034
  ? (COLOR_TOKENS[color].flatFgDarkClass ?? "")
752
1035
  : "";
753
- const style = vars
1036
+ const baseStyle = vars
754
1037
  ? shape === "outline"
755
1038
  ? { ...vars, borderColor: vars["--chip-fg"] }
756
1039
  : vars
757
1040
  : undefined;
758
- return (jsxRuntime.jsxs(react.Chip, { id: id, size: size, variant: V3_VARIANT_BY_SHAPE[shape],
1041
+ // El `style` del consumidor va DESPUÉS: pisa el relleno/texto calculados (uso real en
1042
+ // beweossmbs: `style={{ backgroundColor, color }}` con el color del agente, 5 sitios).
1043
+ const style = baseStyle || consumerStyle ? { ...baseStyle, ...consumerStyle } : undefined;
1044
+ const shadowClass = variant === "shadow"
1045
+ ? isStandard
1046
+ ? SHADOW_CLASS$1[color]
1047
+ : "shadow-lg"
1048
+ : "";
1049
+ // `as` de v2 (`Component = as || "div"`). v3 no tiene `as`, pero su raíz acepta `render`, que
1050
+ // recibe las props DOM ya compuestas (clases, `data-slot`, `ref`…) y devuelve el elemento.
1051
+ const render = as
1052
+ ? (props) => $8WNJc$react.createElement(as, props)
1053
+ : undefined;
1054
+ return (jsxRuntime.jsxs(react.Chip
1055
+ // `aria-disabled` ANTES del spread: si el consumidor manda el suyo, gana el suyo.
1056
+ , { "aria-disabled": isDisabled || undefined, ...domProps, render: render, size: size, variant: V3_VARIANT_BY_SHAPE[shape],
759
1057
  // `cn` (tailwind-merge) y no una concatenación de texto: el consumidor tiene que poder
760
1058
  // pisar tamaño y radio (`className="h-5 text-[10px]"` es un uso real en beweossmbs).
761
1059
  // Con una cadena suelta ambas clases llegan al DOM y gana la que Tailwind emita más
@@ -764,18 +1062,20 @@ const Chip = ({ color = "default", variant = "solid", size = "md", radius = "ful
764
1062
  className: react.cn(SIZE_CLASS$6[size],
765
1063
  // Después de `SIZE_CLASS` a propósito: el cuadrado de un carácter ENCOGE la caja
766
1064
  // respecto al tamaño normal y tiene que ganar en tailwind-merge.
767
- isSquare ? ONE_CHAR_CLASS[size] : "", RADIUS_CLASS$9[radius], outlineClass, flatDarkFgClass, customClass, isDisabled ? "opacity-50 pointer-events-none" : "", classNames?.base, className), style: style, "aria-disabled": isDisabled || undefined, children: [startContent, jsxRuntime.jsx("span", { className: react.cn("flex-1 text-inherit font-normal", startContent
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
768
1066
  ? CONTENT_PADDING[size].leftWithContent
769
1067
  : CONTENT_PADDING[size].left, hasEndContent
770
1068
  ? CONTENT_PADDING[size].rightWithContent
771
1069
  : CONTENT_PADDING[size].right,
772
1070
  // `content: "px-0 flex-none"` del compound de un carácter. Va después de los
773
1071
  // `pl-*`/`pr-*` para que tailwind-merge los borre (un `px` posterior sí gana).
774
- isSquare ? "px-0 flex-none" : "", classNames?.label), children: children }), endContent, onClose && (jsxRuntime.jsx(react.CloseButton, { "aria-label": "Cerrar",
1072
+ isSquare ? "px-0 flex-none" : "", classNames?.label,
1073
+ // `content` es el nombre v2 del slot (16 usos en beweossmbs); va el último.
1074
+ classNames?.content), children: children }), endContent, onClose && (jsxRuntime.jsx(react.CloseButton, { "aria-label": "Cerrar",
775
1075
  // `.close-button` de v3 impone caja de 24px, `bg-default` y `text-muted`; v2 no
776
1076
  // pintaba fondo ni caja: el glifo ERA el botón. Se anulan con utilidades (capa
777
1077
  // `utilities` gana a `components`, incluido el `& svg { size-4 -mx-0.5 my-0.5 }`).
778
- className: "h-auto w-auto min-h-0 rounded-full bg-transparent p-0 text-current opacity-70 transition-opacity hover:bg-transparent hover:opacity-100", onPress: onClose, isDisabled: isDisabled, children: jsxRuntime.jsx("svg", { "aria-hidden": "true", focusable: "false", role: "presentation", viewBox: "0 0 24 24", className: `m-0 shrink-0 ${CLOSE_ICON_SIZE_CLASS[size]}`, children: jsxRuntime.jsx("path", { d: CLOSE_FILLED_PATH, fill: "currentColor" }) }) }))] }));
1078
+ className: react.cn("h-auto w-auto min-h-0 rounded-full bg-transparent p-0 text-current opacity-70 transition-opacity hover:bg-transparent hover:opacity-100", classNames?.closeButton), onPress: onClose, isDisabled: isDisabled, children: jsxRuntime.jsx("svg", { "aria-hidden": "true", focusable: "false", role: "presentation", viewBox: "0 0 24 24", className: `m-0 shrink-0 ${CLOSE_ICON_SIZE_CLASS[size]}`, children: jsxRuntime.jsx("path", { d: CLOSE_FILLED_PATH, fill: "currentColor" }) }) }))] }));
779
1079
  };
780
1080
 
781
1081
  const sizeMap = {
@@ -816,10 +1116,10 @@ const IconComponent = ({ icon, size = "md", className, style, color, hFlip, vFli
816
1116
  // (`bg-default-200` / `bg-default-300` en variant `flat`) se reponen también por clase por el
817
1117
  // mismo motivo.
818
1118
  //
819
- // El RADIO no entra aquí a propósito: se compone por pieza más abajo. Estas clases se concatenan
820
- // como texto (no con `cn`/tailwind-merge), así que dos radios en la misma cadena no se resuelven
821
- // por orden de escritura sino por el orden en que Tailwind los emite — `rounded-none` ganaba a
822
- // `rounded-[12px]` y la celda activa salía a escuadra.
1119
+ // El RADIO no entra aquí a propósito: se compone por pieza más abajo (`cellRadius`). Las piezas se
1120
+ // fusionan con `cn` (tailwind-merge), que resuelve dos clases del mismo grupo por su POSICIÓN en
1121
+ // la cadena. Antes se concatenaban como texto y decidía el orden en que Tailwind las emite:
1122
+ // `rounded-none` ganaba a `rounded-[12px]` y la celda activa salía a escuadra.
823
1123
  // ⚠️ TECH-2459 · F5 · R2 — `shadow-xs`, no `shadow-sm`: **Tailwind 4 corrió la escala de sombras
824
1124
  // un paso**. El `shadow-sm` de Tailwind 3 (que es el que emitía v2) es el `shadow-xs` de Tailwind
825
1125
  // 4. Literal de `node_modules/tailwindcss/theme.css:406-407`:
@@ -844,11 +1144,38 @@ const CELL_CLASS = "size-9 md:size-9 min-w-9 bg-default-100 text-sm text-default
844
1144
  * `w-fit` reproduce el `max-w-fit` del wrapper de v2.
845
1145
  */
846
1146
  const STRIP_CLASS = "w-fit gap-0 rounded-[12px] shadow-xs";
847
- /** Celdas interiores: a escuadra (el `[&:not(:first-of-type):not(:last-of-type)]:rounded-none` de v2). */
848
- const INNER_RADIUS_CLASS = "rounded-none";
849
- /** Extremos de la tira: solo el lado exterior lleva radio, como el `isCompact` de v2. */
850
- const NAV_START_CLASS = `${CELL_CLASS} w-9 px-0 rounded-s-[12px] rounded-e-none`;
851
- const NAV_END_CLASS = `${CELL_CLASS} w-9 px-0 rounded-e-[12px] rounded-s-none`;
1147
+ /**
1148
+ * `isCompact={false}`: celdas sueltas. Literales del `tv` de v2 sin el bloque `isCompact`: el
1149
+ * `wrapper` vuelve a `gap-1` (4px) sin sombra común, y cada celda lleva su propio radio
1150
+ * (`radius: md` → `rounded-medium`, 12px).
1151
+ */
1152
+ const STRIP_LOOSE_CLASS = "w-fit gap-1";
1153
+ /** Controles anterior/siguiente: celda cuadrada, sin el padding lateral de v3. */
1154
+ const NAV_CLASS = "w-9 px-0";
1155
+ /**
1156
+ * Radio de cada pieza según su posición en la tira.
1157
+ *
1158
+ * Compacta (el `isCompact` de v2): celdas interiores a escuadra
1159
+ * (`[&:not(:first-of-type):not(:last-of-type)]:rounded-none`) y solo el lado EXTERIOR de los
1160
+ * extremos redondeado (`first-of-type:rounded-e-none` / `last-of-type:rounded-s-none`). Los
1161
+ * extremos son las flechas cuando hay `showControls`, y las propias páginas cuando no: por eso
1162
+ * se decide por posición y no por tipo de pieza.
1163
+ */
1164
+ function cellRadius(isCompact, position) {
1165
+ if (!isCompact) {
1166
+ return "rounded-[12px]";
1167
+ }
1168
+ if (position.isFirst && position.isLast) {
1169
+ return "rounded-[12px]";
1170
+ }
1171
+ if (position.isFirst) {
1172
+ return "rounded-s-[12px] rounded-e-none";
1173
+ }
1174
+ if (position.isLast) {
1175
+ return "rounded-e-[12px] rounded-s-none";
1176
+ }
1177
+ return "rounded-none";
1178
+ }
852
1179
  /**
853
1180
  * Página activa. v2 la pintaba con un "cursor" absoluto de `bg-primary text-primary-foreground` y
854
1181
  * `rounded-medium` por encima de la celda; aquí se pinta la celda misma con los mismos tokens.
@@ -856,7 +1183,39 @@ const NAV_END_CLASS = `${CELL_CLASS} w-9 px-0 rounded-e-[12px] rounded-s-none`;
856
1183
  * Sin esto v3 la pinta con `--pagination-link-bg: var(--default)`, el gris CRUDO de v3 y no el
857
1184
  * `--color-default` de Aurora (falso amigo, decisiones-aceptadas-f1.md D-F1-3).
858
1185
  */
859
- const ACTIVE_LINK_CLASS = "rounded-[12px] bg-primary text-primary-foreground hover:bg-primary active:bg-primary";
1186
+ const ACTIVE_LINK_CLASS = "rounded-[12px]";
1187
+ /**
1188
+ * `color` de v2 → la página activa. Literal de `colorVariants.solid` (`@heroui/theme@2.4.15`,
1189
+ * `dist/components/pagination.js`): `bg-<color> text-<color>-foreground`. Los estados hover y
1190
+ * pulsado se fijan al mismo fondo porque la celda base los cambia (`CELL_CLASS`) y en v2 el
1191
+ * cursor activo no reaccionaba al ratón. Cadenas literales completas: Tailwind solo emite lo que
1192
+ * encuentra escrito entero.
1193
+ *
1194
+ * `default` es el gris de Aurora (`--color-default`), no el `--default` crudo de v3 (falso
1195
+ * amigo, decisiones-aceptadas-f1.md D-F1-3).
1196
+ */
1197
+ const ACTIVE_COLOR_CLASS = {
1198
+ default: "bg-default text-default-foreground hover:bg-default active:bg-default",
1199
+ primary: "bg-primary text-primary-foreground hover:bg-primary active:bg-primary",
1200
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary active:bg-secondary",
1201
+ success: "bg-success text-success-foreground hover:bg-success active:bg-success",
1202
+ warning: "bg-warning text-warning-foreground hover:bg-warning active:bg-warning",
1203
+ danger: "bg-danger text-danger-foreground hover:bg-danger active:bg-danger",
1204
+ };
1205
+ /**
1206
+ * `showShadow` de v2 → sombra de color bajo la página activa. Literal del compound
1207
+ * `{ showShadow: true, color }` de v2: `cursor: [colorVariants.shadow[color], "shadow-md"]`, que
1208
+ * tras tailwind-merge queda `shadow-md shadow-<color>/40` (`/50` en `default`). `shadow-md` no
1209
+ * cambió de valor entre Tailwind 3 y 4 (el corrimiento de la escala solo afectó a `sm`/`DEFAULT`).
1210
+ */
1211
+ const ACTIVE_SHADOW_CLASS = {
1212
+ default: "shadow-md shadow-default/50",
1213
+ primary: "shadow-md shadow-primary/40",
1214
+ secondary: "shadow-md shadow-secondary/40",
1215
+ success: "shadow-md shadow-success/40",
1216
+ warning: "shadow-md shadow-warning/40",
1217
+ danger: "shadow-md shadow-danger/40",
1218
+ };
860
1219
  /**
861
1220
  * Traducciones por defecto en español. Todas son `aria-label`: este componente no tiene más
862
1221
  * texto visible que los números de página.
@@ -968,12 +1327,22 @@ function getPaginationRange(total, current) {
968
1327
  * `aria-label` los dos botones llegaban vacíos al árbol de accesibilidad.
969
1328
  *
970
1329
 
1330
+ * API v2 que las apps siguen escribiendo (medido en beweossmbs `origin/staging`: 17 usos, todos
1331
+ * con `showControls`, 14 con `isCompact`, 12 con `color="primary"`, 11 con `showShadow`):
1332
+ * - `showControls` (default `true`): pinta anterior/siguiente; con `false` desaparecen.
1333
+ * - `isCompact` (default `true`): tira compacta; con `false`, celdas sueltas (`gap-1`).
1334
+ * - `color` (default `"primary"`): color de la página activa.
1335
+ * - `showShadow` (default `false`): sombra de color bajo la página activa.
1336
+ * Los defaults `true` de las dos primeras son los que forzaba la fachada v2 de Aurora
1337
+ * (`<HeroUIPagination showControls isCompact {...props} />`), así que quien no las pasa se ve
1338
+ * exactamente igual que hasta ahora.
1339
+ *
971
1340
  * ⚠️ `size` se acepta pero no se aplica: mismo bug preexistente que v2 (destructurado, nunca
972
1341
  * reenviado a HeroUI) — fuera de scope de esta migración, reportado aparte (ver
973
1342
  * findings/decisiones-aceptadas-g2.md y f5-L7-sueltos.md · P-17). v3 SÍ tiene la prop (emite
974
1343
  * `.pagination--sm|--md|--lg`), así que arreglarlo es un cambio de tamaño visible: ticket aparte.
975
1344
  */
976
- const Pagination = ({ total, page, initialPage = 1, onChange, size: _size = "sm", className, isDisabled, translations = {}, }) => {
1345
+ const Pagination = ({ total, page, initialPage = 1, onChange, size: _size = "sm", className, isDisabled, showControls = true, isCompact = true, showShadow = false, color = "primary", translations = {}, }) => {
977
1346
  const [internalPage, setInternalPage] = $8WNJc$react.useState(initialPage);
978
1347
  const currentPage = page ?? internalPage;
979
1348
  const t = { ...DEFAULT_TRANSLATIONS$g, ...translations };
@@ -990,18 +1359,26 @@ const Pagination = ({ total, page, initialPage = 1, onChange, size: _size = "sm"
990
1359
  // `dist/pagination.js:464`). Si `page` viene fuera de rango, `indexOf` da -1 y todas las
991
1360
  // elipsis avanzan — también igual que v2, que no protegía ese caso.
992
1361
  const activeIndex = items.indexOf(currentPage);
1362
+ // Posición de cada pieza en la tira, para el radio compacto: con controles, los extremos
1363
+ // son las flechas y ninguna página toca el borde.
1364
+ const lastItemIndex = items.length - 1;
1365
+ const itemPosition = (index) => ({
1366
+ isFirst: !showControls && index === 0,
1367
+ isLast: !showControls && index === lastItemIndex,
1368
+ });
1369
+ const activeClass = react.cn(ACTIVE_COLOR_CLASS[color], showShadow ? ACTIVE_SHADOW_CLASS[color] : "", ACTIVE_LINK_CLASS);
993
1370
  return (
994
1371
  // `w-fit` reproduce el `max-w-fit` del wrapper de v2: `.pagination` de v3 es `w-full` +
995
1372
  // `justify-between`, así que dentro de un `flex justify-center` (como el de AccordionList) la
996
1373
  // tira se quedaba pegada a la izquierda en vez de centrarse.
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
1374
+ jsxRuntime.jsx(react.Pagination, { className: react.cn("w-fit", className), children: jsxRuntime.jsxs(react.Pagination.Content, { className: isCompact ? STRIP_CLASS : STRIP_LOOSE_CLASS, children: [showControls && (jsxRuntime.jsx(react.Pagination.Item, { children: jsxRuntime.jsx(react.Pagination.Previous
998
1375
  // v3 mete el galón en un `<span aria-hidden="true">`
999
1376
  // (`pagination.js:165`), así que sin esto el botón se queda SIN nombre
1000
1377
  // accesible: un lector de pantalla lo anuncia como "botón" a secas. v2 sí
1001
1378
  // lo nombraba ("previous page button", `use-pagination.js:213`), pero en
1002
1379
  // inglés y hardcodeado; aquí va por `translations` (regla obligatoria de
1003
1380
  // 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) => {
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) => {
1005
1382
  if (item === "ellipsis") {
1006
1383
  const isBefore = index < activeIndex;
1007
1384
  const label = isBefore
@@ -1009,14 +1386,14 @@ const Pagination = ({ total, page, initialPage = 1, onChange, size: _size = "sm"
1009
1386
  : t.jumpForwardAriaLabel;
1010
1387
  return (
1011
1388
  // 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
1389
+ jsxRuntime.jsx(react.Pagination.Item, { children: jsxRuntime.jsxs(react.Pagination.Link, { "aria-label": label.replace("{count}", String(DOTS_JUMP)), className: react.cn("group", CELL_CLASS, cellRadius(isCompact, itemPosition(index))), isDisabled: isDisabled, onPress: () => goTo(isBefore
1013
1390
  ? currentPage - DOTS_JUMP
1014
1391
  : currentPage + DOTS_JUMP), children: [jsxRuntime.jsx(EllipsisDotsIcon, {}), jsxRuntime.jsx(JumpIcon, { isBefore: isBefore })] }) }, `ellipsis-${index}`));
1015
1392
  }
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
1393
+ 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
+ }), showControls && (jsxRuntime.jsx(react.Pagination.Item, { children: jsxRuntime.jsx(react.Pagination.Next
1018
1395
  /* 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, {}) }) })] }) }));
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, {}) }) }))] }) }));
1020
1397
  };
1021
1398
 
1022
1399
  // TECH-2459 · F5 · P-01 — Valores tomados del tema v2 REAL (`@heroui/theme@2.4.15`,
@@ -1185,6 +1562,33 @@ exports.HolidayType = void 0;
1185
1562
  HolidayType["DateRange"] = "dateRange";
1186
1563
  })(exports.HolidayType || (exports.HolidayType = {}));
1187
1564
 
1565
+ // Piezas compartidas por `DatePicker` y `DateRangePicker` para reponer `classNames` y
1566
+ // `calendarWidth` de v2 (TECH-2459). Interno: no se exporta desde el índice del paquete.
1567
+ /**
1568
+ * Fusiona las clases de la fachada con las del consumidor (`classNames.<slot>`), las del consumidor
1569
+ * DETRÁS para que ganen en tailwind-merge — como el `slots.x({ class })` de v2, cuyo `tv` llevaba
1570
+ * `twMerge: true`. Sin clase del consumidor la cadena de la fachada sale TAL CUAL (unida con
1571
+ * espacios, como antes de existir `classNames`): pasar la fachada por `cn` sin necesidad resolvería
1572
+ * sus conflictos internos (p. ej. `rounded-none` de `underlined` contra el radio) de otra forma que
1573
+ * la cascada de hoy, y cambiaría el píxel de quien no usa la prop.
1574
+ */
1575
+ function withConsumerClass(own, consumer) {
1576
+ const base = own.filter(Boolean).join(" ");
1577
+ return consumer ? react.cn(base, consumer) : base;
1578
+ }
1579
+ /**
1580
+ * `calendarWidth` de v2 → estilo en línea del `Calendar`. v3 fija `.calendar { w-63 max-w-63 }`
1581
+ * (`@heroui/styles/dist/components/calendar.css:11-13`), así que el `max-width` tiene que acompañar
1582
+ * al `width`: con solo `width: 300px` el tope de 252px lo seguiría recortando.
1583
+ */
1584
+ function calendarWidthStyle(calendarWidth, style) {
1585
+ if (calendarWidth === undefined) {
1586
+ return style;
1587
+ }
1588
+ const width = typeof calendarWidth === "number" ? `${calendarWidth}px` : calendarWidth;
1589
+ return { ...style, width, maxWidth: width };
1590
+ }
1591
+
1188
1592
  // Mismo mecanismo que Input/Select (findings/campo-texto-responsive-sm-trap.md):
1189
1593
  // `.date-input-group__input` fija su propio `text-base sm:text-sm` — se repite la clase de
1190
1594
  // tamaño de TEXTO directo en `DateField.Input`, no solo en el `DateField.Group` contenedor.
@@ -1233,6 +1637,44 @@ const VARIANT_CLASS$5 = {
1233
1637
  * En reposo no cambia nada (el anillo de v3 ya vale 0 ahí), así que la referencia v2 se mantiene.
1234
1638
  */
1235
1639
  const FIELD_FOCUS_CLASS$5 = "ring-0 ring-offset-0 outline-0";
1640
+ /*
1641
+ * TECH-2459 · hallazgo beweossmbs 23-sep — `labelPlacement` de v2, repuesta. Misma solución que
1642
+ * `TimeInput` en la .139: una tabla de clases por colocación. La barra de la agenda
1643
+ * (`agenda-toolbar.component.tsx`) pide `"outside-left"` y v1 no tenía la prop: la etiqueta caía
1644
+ * encima y la barra crecía una línea.
1645
+ *
1646
+ * `outside` es EXACTAMENTE lo que la fachada pintaba hasta ahora (la raíz `flex flex-col gap-1`,
1647
+ * sin clases de colocación en el grupo ni en la ayuda), así que quien no pasa la prop no cambia un
1648
+ * píxel. `outside-left` son los literales del `tv` de v2 (`@heroui/theme@2.4.15` →
1649
+ * `dist/components/date-input.js`, `labelPlacement["outside-left"]`, que es el `tv` del que tira el
1650
+ * `DatePicker` de v2):
1651
+ *
1652
+ * base → "flex-row items-center gap-x-2 flex-nowrap"
1653
+ * + `data-[has-helper=true]:pb-[calc(theme(fontSize.tiny)_+_8px)]`
1654
+ * inputWrapper → "relative flex-1"
1655
+ * helperWrapper→ "absolute top-[calc(100%_+_2px)] start-0"
1656
+ *
1657
+ * La ayuda y el error no pueden ser un tercer hijo de la fila (quedarían a la derecha del campo):
1658
+ * v2 los sacaba del flujo bajo el campo y reservaba su alto con el `pb` de la raíz. Aquí igual;
1659
+ * `theme(fontSize.tiny)` de v2 (12px) es `--text-xs` en Tailwind 4, y la raíz lleva `relative`
1660
+ * para que el `absolute` se ancle a ella.
1661
+ */
1662
+ const ROOT_PLACEMENT_CLASS = {
1663
+ outside: "flex flex-col gap-1",
1664
+ "outside-left": "relative flex flex-row items-center gap-x-2 flex-nowrap",
1665
+ };
1666
+ const ROOT_HELPER_SPACE_CLASS = {
1667
+ outside: undefined,
1668
+ "outside-left": "pb-[calc(var(--text-xs)+8px)]",
1669
+ };
1670
+ const GROUP_PLACEMENT_CLASS = {
1671
+ outside: undefined,
1672
+ "outside-left": "relative flex-1",
1673
+ };
1674
+ const HELPER_PLACEMENT_CLASS = {
1675
+ outside: undefined,
1676
+ "outside-left": "absolute top-[calc(100%+2px)] start-0",
1677
+ };
1236
1678
  // TECH-2459 · F5 · P-10 — El "círculo" del calendario. v2 (`@heroui/theme@2.4.15`,
1237
1679
  // `calendar.ts`) solo tenía UN círculo y significaba "día seleccionado":
1238
1680
  // cellButton: "w-8 h-8 … rounded-full" + color primary: "data-[selected=true]:bg-primary
@@ -1259,20 +1701,32 @@ const CALENDAR_CELL_CLASS$1 = "size-8 place-self-center rounded-full " +
1259
1701
  * (grid) para que la API pública siga siendo un único componente con props planas, igual que en
1260
1702
  * v2 (label, description, errorMessage, endContent, variant, size, radius).
1261
1703
  */
1262
- function DatePicker({ label, description, errorMessage, id, size = "md", variant = "bordered", radius = "md", isInvalid = false, isRequired = false, isDisabled = false, fullWidth, endContent, calendarBottomContent, className, "aria-label": ariaLabel, ...props }) {
1704
+ function DatePicker({ label, description, errorMessage, id, size = "md", variant = "bordered", radius = "md", labelPlacement = "outside", isInvalid = false, isRequired = false, isDisabled = false, fullWidth, endContent, calendarBottomContent: calendarBottomContentProp, CalendarBottomContent, calendarTopContent: calendarTopContentProp, CalendarTopContent, calendarWidth, calendarProps, classNames, className, "aria-label": ariaLabel, ...props }) {
1263
1705
  const generatedId = $8WNJc$react.useId();
1264
1706
  const autoId = id || generatedId;
1265
- const calendarLabel = (typeof label === "string" ? label : undefined) ?? ariaLabel ?? "Calendario";
1266
- return (jsxRuntime.jsxs(react.DatePicker, { ...props, id: autoId, "aria-label": ariaLabel, isInvalid: isInvalid, isRequired: isRequired, isDisabled: isDisabled, className: ["flex flex-col gap-1", fullWidth ? "w-full" : "", className]
1267
- .filter(Boolean)
1268
- .join(" "), children: [label && (jsxRuntime.jsx(react.Label, { className: "text-default-600 text-xs font-medium", children: label })), jsxRuntime.jsxs(react.DateField.Group, { fullWidth: fullWidth, className: [
1707
+ const calendarLabel = (typeof label === "string" ? label : undefined) ??
1708
+ ariaLabel ??
1709
+ "Calendario";
1710
+ const calendarBottomContent = calendarBottomContentProp ?? CalendarBottomContent;
1711
+ const calendarTopContent = calendarTopContentProp ?? CalendarTopContent;
1712
+ const hasHelper = Boolean((description && !isInvalid) || (isInvalid && errorMessage));
1713
+ const { className: calendarClassName, style: calendarStyle, ...restCalendarProps } = calendarProps ?? {};
1714
+ return (jsxRuntime.jsxs(react.DatePicker, { ...props, id: autoId, "aria-label": ariaLabel, isInvalid: isInvalid, isRequired: isRequired, isDisabled: isDisabled, className: withConsumerClass([
1715
+ ROOT_PLACEMENT_CLASS[labelPlacement],
1716
+ fullWidth && "w-full",
1717
+ hasHelper && ROOT_HELPER_SPACE_CLASS[labelPlacement],
1718
+ classNames?.base,
1719
+ ], className), children: [label && (jsxRuntime.jsx(react.Label, { className: withConsumerClass(["text-default-600 text-xs font-medium"], classNames?.label), children: label })), jsxRuntime.jsxs(react.DateField.Group, { fullWidth: fullWidth, className: withConsumerClass([
1269
1720
  SIZE_CLASS$5[size],
1270
1721
  RADIUS_CLASS$8[radius],
1271
1722
  VARIANT_CLASS$5[variant],
1272
1723
  FIELD_FOCUS_CLASS$5,
1273
- ]
1274
- .filter(Boolean)
1275
- .join(" "), children: [jsxRuntime.jsx(react.DateField.Input, { className: SIZE_TEXT_CLASS$4[size], children: (segment) => jsxRuntime.jsx(react.DateField.Segment, { segment: segment }) }), jsxRuntime.jsx(react.DateField.Suffix, { children: jsxRuntime.jsx(react.DatePicker.Trigger, { children: endContent !== undefined ? (jsxRuntime.jsx(react.DatePicker.TriggerIndicator, { children: endContent })) : (jsxRuntime.jsx(react.DatePicker.TriggerIndicator, {})) }) })] }), description && !isInvalid && jsxRuntime.jsx(react.Description, { children: description }), jsxRuntime.jsxs(react.DatePicker.Popover, { children: [jsxRuntime.jsxs(react.Calendar, { "aria-label": calendarLabel, children: [jsxRuntime.jsxs(react.Calendar.Header, { children: [jsxRuntime.jsxs(react.Calendar.YearPickerTrigger, { children: [jsxRuntime.jsx(react.Calendar.YearPickerTriggerHeading, {}), jsxRuntime.jsx(react.Calendar.YearPickerTriggerIndicator, {})] }), jsxRuntime.jsx(react.Calendar.NavButton, { slot: "previous" }), jsxRuntime.jsx(react.Calendar.NavButton, { slot: "next" })] }), jsxRuntime.jsxs(react.Calendar.Grid, { children: [jsxRuntime.jsx(react.Calendar.GridHeader, { children: (day) => jsxRuntime.jsx(react.Calendar.HeaderCell, { children: day }) }), jsxRuntime.jsx(react.Calendar.GridBody, { children: (date) => (jsxRuntime.jsx(react.Calendar.Cell, { date: date, className: CALENDAR_CELL_CLASS$1 })) })] })] }), calendarBottomContent] }), isInvalid && errorMessage && jsxRuntime.jsx(react.FieldError, { children: errorMessage })] }));
1724
+ GROUP_PLACEMENT_CLASS[labelPlacement],
1725
+ ], classNames?.inputWrapper), children: [jsxRuntime.jsx(react.DateField.Input, { className: withConsumerClass([SIZE_TEXT_CLASS$4[size]], classNames?.input), children: (segment) => (jsxRuntime.jsx(react.DateField.Segment, { segment: segment, className: classNames?.segment })) }), jsxRuntime.jsx(react.DateField.Suffix, { children: jsxRuntime.jsx(react.DatePicker.Trigger, { className: classNames?.selectorButton, children: endContent !== undefined ? (jsxRuntime.jsx(react.DatePicker.TriggerIndicator, { className: classNames?.selectorIcon, children: endContent })) : (jsxRuntime.jsx(react.DatePicker.TriggerIndicator, { className: classNames?.selectorIcon })) }) })] }), description && !isInvalid && (jsxRuntime.jsx(react.Description, { className: withConsumerClass([HELPER_PLACEMENT_CLASS[labelPlacement]], classNames?.description) || undefined, children: description })), jsxRuntime.jsxs(react.DatePicker.Popover, { className: classNames?.popoverContent, children: [calendarTopContent, jsxRuntime.jsxs(react.Calendar, { "aria-label": calendarLabel, minValue: props.minValue, maxValue: props.maxValue, ...restCalendarProps, className: withConsumerClass([
1726
+ typeof calendarClassName === "string"
1727
+ ? calendarClassName
1728
+ : undefined,
1729
+ ], classNames?.calendar) || undefined, style: calendarWidthStyle(calendarWidth, typeof calendarStyle === "object" ? calendarStyle : undefined), children: [jsxRuntime.jsxs(react.Calendar.Header, { children: [jsxRuntime.jsxs(react.Calendar.YearPickerTrigger, { children: [jsxRuntime.jsx(react.Calendar.YearPickerTriggerHeading, {}), jsxRuntime.jsx(react.Calendar.YearPickerTriggerIndicator, {})] }), jsxRuntime.jsx(react.Calendar.NavButton, { slot: "previous" }), jsxRuntime.jsx(react.Calendar.NavButton, { slot: "next" })] }), jsxRuntime.jsxs(react.Calendar.Grid, { children: [jsxRuntime.jsx(react.Calendar.GridHeader, { children: (day) => jsxRuntime.jsx(react.Calendar.HeaderCell, { children: day }) }), jsxRuntime.jsx(react.Calendar.GridBody, { children: (date) => (jsxRuntime.jsx(react.Calendar.Cell, { date: date, className: CALENDAR_CELL_CLASS$1 })) })] })] }), calendarBottomContent] }), isInvalid && errorMessage && (jsxRuntime.jsx(react.FieldError, { className: withConsumerClass([HELPER_PLACEMENT_CLASS[labelPlacement]], classNames?.errorMessage) || undefined, children: errorMessage }))] }));
1276
1730
  }
1277
1731
 
1278
1732
  // Mismo mecanismo que DatePicker.tsx / Input.tsx / Select.tsx (findings/
@@ -1351,20 +1805,25 @@ const CALENDAR_CELL_CLASS = "data-[today=true]:not-data-[selected=true]:[&_[data
1351
1805
  * `DatePicker`, migrado en tándem. Compone `DateRangePicker.Root/Trigger/TriggerIndicator/
1352
1806
  * RangeSeparator/Popover` + `DateField` (dos segmentos, start/end) + `RangeCalendar`.
1353
1807
  */
1354
- function DateRangePicker({ label, description, errorMessage, id, size = "md", variant = "bordered", radius = "md", isInvalid = false, isRequired = false, isDisabled = false, fullWidth, endContent, calendarBottomContent, className, "aria-label": ariaLabel, ...props }) {
1808
+ function DateRangePicker({ label, description, errorMessage, id, size = "md", variant = "bordered", radius = "md", isInvalid = false, isRequired = false, isDisabled = false, fullWidth, endContent, calendarBottomContent: calendarBottomContentProp, CalendarBottomContent, calendarTopContent: calendarTopContentProp, CalendarTopContent, calendarWidth, calendarProps, classNames, className, "aria-label": ariaLabel, ...props }) {
1355
1809
  const generatedId = $8WNJc$react.useId();
1356
1810
  const autoId = id || generatedId;
1357
- const calendarLabel = (typeof label === "string" ? label : undefined) ?? ariaLabel ?? "Rango de fechas";
1358
- return (jsxRuntime.jsxs(react.DateRangePicker, { ...props, id: autoId, "aria-label": ariaLabel, isInvalid: isInvalid, isRequired: isRequired, isDisabled: isDisabled, className: ["flex flex-col gap-1", fullWidth ? "w-full" : "", className]
1359
- .filter(Boolean)
1360
- .join(" "), children: [label && (jsxRuntime.jsx(react.Label, { className: "text-default-600 text-xs font-medium", children: label })), jsxRuntime.jsxs(react.DateField.Group, { fullWidth: fullWidth, className: [
1811
+ const calendarLabel = (typeof label === "string" ? label : undefined) ??
1812
+ ariaLabel ??
1813
+ "Rango de fechas";
1814
+ const calendarBottomContent = calendarBottomContentProp ?? CalendarBottomContent;
1815
+ const calendarTopContent = calendarTopContentProp ?? CalendarTopContent;
1816
+ const { className: calendarClassName, style: calendarStyle, ...restCalendarProps } = calendarProps ?? {};
1817
+ return (jsxRuntime.jsxs(react.DateRangePicker, { ...props, id: autoId, "aria-label": ariaLabel, isInvalid: isInvalid, isRequired: isRequired, isDisabled: isDisabled, className: withConsumerClass(["flex flex-col gap-1", fullWidth && "w-full", classNames?.base], className), children: [label && (jsxRuntime.jsx(react.Label, { className: withConsumerClass(["text-default-600 text-xs font-medium"], classNames?.label), children: label })), jsxRuntime.jsxs(react.DateField.Group, { fullWidth: fullWidth, className: withConsumerClass([
1361
1818
  SIZE_CLASS$4[size],
1362
1819
  RADIUS_CLASS$7[radius],
1363
1820
  VARIANT_CLASS$4[variant],
1364
1821
  FIELD_FOCUS_CLASS$4,
1365
- ]
1366
- .filter(Boolean)
1367
- .join(" "), children: [jsxRuntime.jsx(react.DateField.Input, { slot: "start", className: SIZE_TEXT_CLASS$3[size], children: (segment) => jsxRuntime.jsx(react.DateField.Segment, { segment: segment }) }), jsxRuntime.jsx(react.DateRangePicker.RangeSeparator, {}), jsxRuntime.jsx(react.DateField.Input, { slot: "end", className: SIZE_TEXT_CLASS$3[size], children: (segment) => jsxRuntime.jsx(react.DateField.Segment, { segment: segment }) }), jsxRuntime.jsx(react.DateField.Suffix, { children: jsxRuntime.jsx(react.DateRangePicker.Trigger, { children: endContent !== undefined ? (jsxRuntime.jsx(react.DateRangePicker.TriggerIndicator, { children: endContent })) : (jsxRuntime.jsx(react.DateRangePicker.TriggerIndicator, {})) }) })] }), description && !isInvalid && jsxRuntime.jsx(react.Description, { children: description }), jsxRuntime.jsxs(react.DateRangePicker.Popover, { children: [jsxRuntime.jsxs(react.RangeCalendar, { "aria-label": calendarLabel, children: [jsxRuntime.jsxs(react.RangeCalendar.Header, { children: [jsxRuntime.jsxs(react.RangeCalendar.YearPickerTrigger, { children: [jsxRuntime.jsx(react.RangeCalendar.YearPickerTriggerHeading, {}), jsxRuntime.jsx(react.RangeCalendar.YearPickerTriggerIndicator, {})] }), jsxRuntime.jsx(react.RangeCalendar.NavButton, { slot: "previous" }), jsxRuntime.jsx(react.RangeCalendar.NavButton, { slot: "next" })] }), jsxRuntime.jsxs(react.RangeCalendar.Grid, { children: [jsxRuntime.jsx(react.RangeCalendar.GridHeader, { children: (day) => jsxRuntime.jsx(react.RangeCalendar.HeaderCell, { children: day }) }), jsxRuntime.jsx(react.RangeCalendar.GridBody, { children: (date) => (jsxRuntime.jsx(react.RangeCalendar.Cell, { date: date, className: CALENDAR_CELL_CLASS })) })] })] }), calendarBottomContent] }), isInvalid && errorMessage && jsxRuntime.jsx(react.FieldError, { children: errorMessage })] }));
1822
+ ], classNames?.inputWrapper), children: [jsxRuntime.jsx(react.DateField.Input, { slot: "start", className: withConsumerClass([SIZE_TEXT_CLASS$3[size]], classNames?.input), children: (segment) => (jsxRuntime.jsx(react.DateField.Segment, { segment: segment, className: classNames?.segment })) }), jsxRuntime.jsx(react.DateRangePicker.RangeSeparator, { className: classNames?.separator }), jsxRuntime.jsx(react.DateField.Input, { slot: "end", className: withConsumerClass([SIZE_TEXT_CLASS$3[size]], classNames?.input), children: (segment) => (jsxRuntime.jsx(react.DateField.Segment, { segment: segment, className: classNames?.segment })) }), jsxRuntime.jsx(react.DateField.Suffix, { children: jsxRuntime.jsx(react.DateRangePicker.Trigger, { className: classNames?.selectorButton, children: endContent !== undefined ? (jsxRuntime.jsx(react.DateRangePicker.TriggerIndicator, { className: classNames?.selectorIcon, children: endContent })) : (jsxRuntime.jsx(react.DateRangePicker.TriggerIndicator, { className: classNames?.selectorIcon })) }) })] }), description && !isInvalid && (jsxRuntime.jsx(react.Description, { className: classNames?.description, children: description })), jsxRuntime.jsxs(react.DateRangePicker.Popover, { className: classNames?.popoverContent, children: [calendarTopContent, jsxRuntime.jsxs(react.RangeCalendar, { "aria-label": calendarLabel, minValue: props.minValue, maxValue: props.maxValue, ...restCalendarProps, className: withConsumerClass([
1823
+ typeof calendarClassName === "string"
1824
+ ? calendarClassName
1825
+ : undefined,
1826
+ ], classNames?.calendar) || undefined, style: calendarWidthStyle(calendarWidth, typeof calendarStyle === "object" ? calendarStyle : undefined), children: [jsxRuntime.jsxs(react.RangeCalendar.Header, { children: [jsxRuntime.jsxs(react.RangeCalendar.YearPickerTrigger, { children: [jsxRuntime.jsx(react.RangeCalendar.YearPickerTriggerHeading, {}), jsxRuntime.jsx(react.RangeCalendar.YearPickerTriggerIndicator, {})] }), jsxRuntime.jsx(react.RangeCalendar.NavButton, { slot: "previous" }), jsxRuntime.jsx(react.RangeCalendar.NavButton, { slot: "next" })] }), jsxRuntime.jsxs(react.RangeCalendar.Grid, { children: [jsxRuntime.jsx(react.RangeCalendar.GridHeader, { children: (day) => (jsxRuntime.jsx(react.RangeCalendar.HeaderCell, { children: day })) }), jsxRuntime.jsx(react.RangeCalendar.GridBody, { children: (date) => (jsxRuntime.jsx(react.RangeCalendar.Cell, { date: date, className: CALENDAR_CELL_CLASS })) })] })] }), calendarBottomContent] }), isInvalid && errorMessage && (jsxRuntime.jsx(react.FieldError, { className: classNames?.errorMessage, children: errorMessage }))] }));
1368
1827
  }
1369
1828
 
1370
1829
  // TECH-2459 · F3.0 — v3 divide el `Input` monolítico de v2 en `TextField` (label/description/
@@ -1678,6 +2137,10 @@ const INVALID_TEXT_CLASS$2 = "text-danger";
1678
2137
  * - Radius configurable (v3 no tiene la prop).
1679
2138
  * - `startContent`/`endContent` → `InputGroup.Prefix`/`InputGroup.Suffix`.
1680
2139
  * - `isClearable` → botón de limpiar propio (`CloseButton`); solo en modo controlado (`value`).
2140
+ * - `onClear` (API v2) → basta para mostrar la X; se llama al limpiar (X o Escape) y el foco
2141
+ * vuelve al campo, como en v2.
2142
+ * - `classNames`: los 10 slots de v2, cada uno en su nodo equivalente (tabla en `Input.types.ts`).
2143
+ * - `labelPlacement` (API v2): aceptado y sin efecto — el label siempre va fuera.
1681
2144
  *
1682
2145
  * `ref` apunta al `<input>` REAL (`HTMLInputElement`), no al `InputGroup` ni al `TextField` que lo
1683
2146
  * envuelven: el consumidor que necesita el nodo —insertar texto en la posición del cursor con
@@ -1697,7 +2160,10 @@ const Input = $8WNJc$react.forwardRef(({ label, description, errorMessage, id,
1697
2160
  // `lg` que había aquí venía del `argTypes.defaultValue.summary` de la story, que YA estaba mal
1698
2161
  // en v2. Con "lg" todos los campos de las 4 apps que no pasan `size` salían 8px más altos, con
1699
2162
  // 2px más de padding lateral y el texto a 16px en vez de 14.
1700
- size = "md", variant = "bordered", radius = "md", placeholder, startContent, endContent, isClearable = false, isInvalid = false, isRequired = false, isDisabled = false, isReadOnly = false,
2163
+ size = "md", variant = "bordered", radius = "md", placeholder, startContent, endContent, isClearable: isClearableProp = false, onClear,
2164
+ // Sin efecto (ver `InputProps.labelPlacement`): se saca del spread para que no llegue
2165
+ // al `<input>` como atributo desconocido.
2166
+ labelPlacement: _labelPlacement, isInvalid = false, isRequired = false, isDisabled = false, isReadOnly = false,
1701
2167
  // `fullWidth` por defecto a `true`: HeroUI v2 lo traía en sus `defaultVariants`
1702
2168
  // (verificado en el paquete v2 compilado, `dist/components/input.js`:
1703
2169
  // `defaultVariants: { variant:"flat", color:"default", size:"md", fullWidth:true, … }`, con
@@ -1705,7 +2171,21 @@ size = "md", variant = "bordered", radius = "md", placeholder, startContent, end
1705
2171
  // `undefined` y el campo dejaba de llenar su contenedor: en una fila flex se encogía al ancho
1706
2172
  // de su contenido (medido por S2: 208px dentro de una fila de 592, 336px de hueco). En una
1707
2173
  // columna no se notaba porque el `stretch` del flex lo tapaba — pero afecta a cualquier app.
1708
- fullWidth = true, value, defaultValue, onChange, onValueChange, className, classNames, required, name, type = "text", ...rest }, ref) => {
2174
+ fullWidth = true, value, defaultValue, onChange, onValueChange, className, classNames, required, name, type = "text", onKeyDown, ...rest }, ref) => {
2175
+ // El `<input>` real, también para la fachada: v2 devolvía el foco al campo al limpiar
2176
+ // (`domRef.current?.focus()` en `handleClear`). Se combina con el `ref` del consumidor.
2177
+ const inputRef = $8WNJc$react.useRef(null);
2178
+ const setInputRef = $8WNJc$react.useCallback((node) => {
2179
+ inputRef.current = node;
2180
+ if (typeof ref === "function") {
2181
+ ref(node);
2182
+ }
2183
+ else if (ref) {
2184
+ ref.current = node;
2185
+ }
2186
+ }, [ref]);
2187
+ // v2: `isClearable = !!onClear || originalProps.isClearable` (`use-input.ts`).
2188
+ const isClearable = isClearableProp || Boolean(onClear);
1709
2189
  const generatedId = $8WNJc$react.useId();
1710
2190
  const inputId = id || generatedId;
1711
2191
  // ⚠️ `isRequired` (prop de HeroUI, asterisco visual) y `required` (atributo HTML nativo, solo
@@ -1713,7 +2193,24 @@ fullWidth = true, value, defaultValue, onChange, onValueChange, className, class
1713
2193
  // Detectado por VRT (`FormExample`, que solo pasa `required`): tratarlos como lo mismo aquí
1714
2194
  // introducía un asterisco que v2 no tenía. `isRequired` maneja el asterisco (vía TextField);
1715
2195
  // `required` se reenvía tal cual al `<input>` nativo, sin efecto visual.
1716
- const showClear = isClearable && !isDisabled && !isReadOnly && Boolean(value);
2196
+ // ── El valor lo guarda el `TextField`, no el `<input>` ───────────────────────────────────
2197
+ //
2198
+ // En react-aria el dueño del valor es el `TextField`: `useTextField` lo guarda con
2199
+ // `useControlledState(value, defaultValue ?? "")` y se lo inyecta al `<input>` como `value`
2200
+ // por contexto (`react-aria-components/dist/private/TextField.mjs`). Antes la fachada pasaba
2201
+ // `value`/`defaultValue` al `InputGroup.Input` y ninguno al `TextField`, así que en modo NO
2202
+ // controlado el `TextField` arrancaba en "" y el `<input>` recibía a la vez `value=""` (del
2203
+ // contexto) y `defaultValue`: medido en jsdom, `<Input defaultValue="hola" />` salía VACÍO
2204
+ // ya en el primer render, con el aviso de React «both value and defaultValue». Mismo arreglo
2205
+ // que en `Textarea.tsx`.
2206
+ //
2207
+ // Aquí además la fachada lleva el valor no controlado en su propio estado (en vez de
2208
+ // dejárselo al `TextField`), porque limpiar tiene que poder vaciarlo: `isClearable`/`onClear`
2209
+ // en modo no controlado no tenían manera de escribir en el estado interno del `TextField`.
2210
+ const isControlled = value !== undefined;
2211
+ const [uncontrolledValue, setUncontrolledValue] = $8WNJc$react.useState(defaultValue ?? "");
2212
+ const currentValue = isControlled ? value : uncontrolledValue;
2213
+ const showClear = isClearable && !isDisabled && !isReadOnly && Boolean(currentValue);
1717
2214
  // Solo flota cuando hay label y NO hay placeholder: con placeholder, v2 ya mostraba el label
1718
2215
  // arriba desde el primer render (ver el bloque de arriba).
1719
2216
  const labelFlotante = Boolean(label) && !placeholder;
@@ -1721,7 +2218,12 @@ fullWidth = true, value, defaultValue, onChange, onValueChange, className, class
1721
2218
  onChange?.(event);
1722
2219
  onValueChange?.(event.target.value);
1723
2220
  };
1724
- const handleClear = () => {
2221
+ // Orden de v2 (`handleClear` de `use-input.ts`): primero el valor a "" —que en v2 disparaba
2222
+ // `onValueChange`—, luego `onClear`, y el foco vuelve al campo.
2223
+ const clearValue = () => {
2224
+ if (!isControlled) {
2225
+ setUncontrolledValue("");
2226
+ }
1725
2227
  onValueChange?.("");
1726
2228
  if (onChange) {
1727
2229
  const fakeEvent = {
@@ -1730,15 +2232,38 @@ fullWidth = true, value, defaultValue, onChange, onValueChange, className, class
1730
2232
  };
1731
2233
  onChange(fakeEvent);
1732
2234
  }
2235
+ onClear?.();
2236
+ };
2237
+ const handleClear = () => {
2238
+ clearValue();
2239
+ inputRef.current?.focus();
2240
+ };
2241
+ // Escape limpia, igual que v2 (`handleKeyDown` de `use-input.ts`: con valor, limpiable y
2242
+ // no de solo lectura). El `onKeyDown` del consumidor se llama primero.
2243
+ const handleKeyDown = (event) => {
2244
+ onKeyDown?.(event);
2245
+ if (event.key === "Escape" && showClear) {
2246
+ clearValue();
2247
+ }
1733
2248
  };
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" : "",
2249
+ return (jsxRuntime.jsxs(react.TextField, { id: inputId, value: currentValue,
2250
+ // Solo el modo no controlado escribe aquí; en controlado manda el `value` del padre
2251
+ // (que se entera por `onChange`/`onValueChange` del `<input>`).
2252
+ 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" : "",
2253
+ // `mainWrapper` de v2 no tiene nodo propio aquí (ver tabla en `Input.types.ts`):
2254
+ // se fusiona en la raíz antes que `base`, que es el slot más externo.
2255
+ classNames?.mainWrapper,
2256
+ // `base` de v2 = la raíz. v2 los juntaba así: `clsx(classNames.base, className)`.
2257
+ classNames?.base,
1735
2258
  // El `className` del consumidor, en la raíz y en último lugar: el slot `base` de
1736
2259
  // v2 y la posición que le deja ganar en tailwind-merge (ver bloque de arriba).
1737
2260
  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$3[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
2261
  // Incondicional, como el `inputWrapper` de v2: la caja del campo llena
1739
2262
  // siempre su raíz, y es la raíz la que responde a `fullWidth` y al ancho que
1740
2263
  // 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$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: "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 }))] }));
2264
+ "w-full",
2265
+ // 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 }))] }));
1742
2267
  });
1743
2268
  Input.displayName = "Input";
1744
2269
 
@@ -1845,12 +2370,36 @@ const RADIUS_CLASS$5 = {
1845
2370
  // v3 no tiene prop `shadow`: su Card siempre usa `shadow-surface`. Los valores de v2 se
1846
2371
  // reproducen con los tokens propios `--aurora-shadow-*` (emitidos en F1, mismo valor exacto que
1847
2372
  // generaba el plugin `heroui()` de v2 — ver findings/tabla-mapeo-tokens.md §2).
2373
+ //
2374
+ // ⚠️ El prefijo `shadow:` dentro del corchete NO es decorativo. Sin él, tailwind-merge 3 clasifica
2375
+ // `shadow-[var(--x)]` como COLOR de sombra (grupo `shadow-color`), no como sombra: medido con el
2376
+ // `cn` real, `cn("shadow-[var(--aurora-shadow-small)]", "shadow-2xl")` conserva LAS DOS, y en el
2377
+ // CSS de Tailwind 4 la arbitraria se emite después de `shadow-2xl` (orden de candidato), así que
2378
+ // ganaba la nuestra — el `shadow-2xl` que pide `whatsapp-setup-modal` no se veía. Con la etiqueta
2379
+ // `shadow:` tailwind-merge la reconoce como sombra y la retira cuando la app pasa la suya; el CSS
2380
+ // que emite Tailwind 4 es idéntico (`--tw-shadow: var(--aurora-shadow-small)`, comprobado con
2381
+ // `@tailwindcss/node`).
1848
2382
  const SHADOW_CLASS = {
1849
2383
  none: "shadow-none",
1850
- sm: "shadow-[var(--aurora-shadow-small)]",
1851
- md: "shadow-[var(--aurora-shadow-medium)]",
1852
- lg: "shadow-[var(--aurora-shadow-large)]",
1853
- };
2384
+ sm: "shadow-[shadow:var(--aurora-shadow-small)]",
2385
+ md: "shadow-[shadow:var(--aurora-shadow-medium)]",
2386
+ lg: "shadow-[shadow:var(--aurora-shadow-large)]",
2387
+ };
2388
+ // Literales del `tv` de v2 (`@heroui/theme@2.4.15`, `dist/components/card.js`), con sus tokens
2389
+ // traducidos a los que existen en Tailwind 4 + Aurora:
2390
+ // isPressable → `cursor-pointer` + compound `data-[pressed=true]:scale-[0.97]
2391
+ // tap-highlight-transparent` (el hundido lo hace aquí `:active`, que es lo que
2392
+ // marcaba `data-pressed` en un botón nativo)
2393
+ // disableAnimation=false → `transition-transform-background motion-reduce:transition-none`
2394
+ // foco → `dataFocusVisibleClasses` (`outline-2 outline-focus outline-offset-2`)
2395
+ // isHoverable → `data-[hover=true]:bg-content2` (aquí `hover:`, react-aria ya no escribe
2396
+ // `data-hover` — trampa 3 de la skill `auraui-components`)
2397
+ // isDisabled → `opacity-disabled cursor-not-allowed` (`opacity-disabled` era 0.5 en v2)
2398
+ // 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]";
2400
+ const PRESS_ANIMATION_CLASS = "transition-[transform,background-color] motion-reduce:transition-none active:scale-[0.97]";
2401
+ const HOVERABLE_CLASS = "hover:bg-content2";
2402
+ const DISABLED_CLASS = "opacity-50 cursor-not-allowed";
1854
2403
  /**
1855
2404
  * Card genérica siguiendo las reglas de diseño BeweOS basada en HeroUI v3.
1856
2405
  *
@@ -1864,18 +2413,50 @@ const SHADOW_CLASS = {
1864
2413
  * - Shadow: none, sm, md, lg (configurable, reimplementado vía className — v3 no tiene la prop)
1865
2414
  * - Radius: sm (por defecto, configurable, reimplementado vía className — v3 no tiene la prop)
1866
2415
  * - Padding configurable (sm, md, lg)
2416
+ * - `className` de la app AL FINAL y fusionado con `cn` (tailwind-merge), como el `slots.base({
2417
+ * class })` de v2: un `p-0`/`rounded-xl`/`shadow-2xl` de la app retira el default de la fachada
2418
+ * en vez de competir con él por orden de CSS.
2419
+ * - `ref` y TODAS las props DOM (`style`, `onMouse*`, `aria-*`, `data-*`…) llegan al nodo raíz,
2420
+ * como en v2 (`HTMLHeroUIProps<"div">`).
2421
+ * - API v2 de card pulsable: `isPressable`/`onPress`/`disableAnimation`/`disableRipple`, más
2422
+ * `isHoverable`/`isDisabled`/`fullWidth`.
2423
+ *
2424
+ * `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>`.
1867
2426
  */
1868
- const Card = ({ children, className = "", shadow = "sm", radius = "sm", padding = "md", ...props }) => {
1869
- const combinedClassName = [
1870
- paddingClasses[padding],
1871
- RADIUS_CLASS$5[radius],
1872
- SHADOW_CLASS[shadow],
1873
- className,
1874
- ]
1875
- .filter(Boolean)
1876
- .join(" ");
1877
- return (jsxRuntime.jsx(react.Card, { className: combinedClassName, ...props, children: children }));
1878
- };
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) => {
2428
+ // Como en v2 (`@heroui/card@2.2.21`, `useCard`): `disableAnimation` apaga también la onda.
2429
+ const sinRipple = disableRipple || disableAnimation;
2430
+ const { ondas, lanzar, quitar } = useRipple();
2431
+ const handleClick = $8WNJc$react.useCallback((event) => {
2432
+ onClick?.(event);
2433
+ if (!isPressable || isDisabled) {
2434
+ return;
2435
+ }
2436
+ const pressEvent = pressEventFromClick(event);
2437
+ onPress?.(pressEvent);
2438
+ if (!sinRipple) {
2439
+ lanzar(pressEvent);
2440
+ }
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 : "",
2443
+ // El `className` de la app, último: gana por fusión (ver JSDoc).
2444
+ 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 }))] }));
2458
+ });
2459
+ Card.displayName = "Card";
1879
2460
 
1880
2461
  const H2 = ({ children, className, ...props }) => {
1881
2462
  return (jsxRuntime.jsx("h2", { className: react.cn("text-lg font-semibold", className), ...props, children: children }));
@@ -2113,10 +2694,221 @@ function withCollectionItemIds(children) {
2113
2694
  return Object.keys(patch).length > 0 ? $8WNJc$react.cloneElement(element, patch) : element;
2114
2695
  }
2115
2696
 
2697
+ /**
2698
+ * Repone en los items y secciones de una colección de v3 la API de CONTENIDO de v2 que las apps
2699
+ * siguen escribiendo en `SelectItem`/`AutocompleteItem`/`SelectSection` (TECH-2459, hallazgo
2700
+ * beweossmbs 23-sep).
2701
+ *
2702
+ * QUÉ HACÍA v2 (`@heroui/listbox@2.3.31`, `dist/chunk-2TBDFBA3.mjs`, leído del tarball):
2703
+ *
2704
+ * <li …getItemProps()>
2705
+ * {startContent && <span data-slot="startContent">…}
2706
+ * {description
2707
+ * ? <div wrapper><span title>{rendered}</span><span description>{description}</span></div>
2708
+ * : <span title>{rendered}</span>}
2709
+ * {selectedIcon}
2710
+ * {endContent && <span data-slot="endContent">…}
2711
+ * </li>
2712
+ *
2713
+ * con los slots del `tv` (`@heroui/theme@2.4.15`, `dist/components/listbox.js:100-139`):
2714
+ * `wrapper: "w-full flex flex-col items-start justify-center"`, `title: "flex-1 text-small
2715
+ * font-normal"`, `description: "w-full text-tiny text-foreground-500 …"`. La sección
2716
+ * (`chunk-Q2EVG7SO.mjs`) pintaba la cabecera `heading: "pl-1 text-tiny text-foreground-500"` y,
2717
+ * con `showDivider` (default `false`), un `Divider` con `mt-2` al final.
2718
+ *
2719
+ * QUÉ HACE v3. `ListBox.Item` no tiene NINGUNA de esas props (la guía oficial: «Place icons
2720
+ * manually in item content», «Use `Description` component»; y `title` de la sección → `Header`).
2721
+ * Pasadas tal cual se descartan en silencio: el `filterDOMProps` de react-aria no las deja llegar
2722
+ * al DOM y el item pinta solo su texto.
2723
+ *
2724
+ * POR QUÉ AQUÍ Y NO EN EL SHIM. `SelectItem`/`AutocompleteItem`/`SelectSection` tienen que seguir
2725
+ * siendo el MISMO objeto que `ListBoxItem`/`ListBoxSection`: el normalizador de ids
2726
+ * (`utils/collection-items.ts`) y el indicador (`with-item-indicator.tsx`) reconocen los items por
2727
+ * identidad de `element.type`, y un envoltorio los volvería invisibles (los items dentro de una
2728
+ * sección perderían su `id` derivado de la `key`, que es el defecto nº 1 de la migración). Así que
2729
+ * el shim solo TIPA las props v2 y esta función, llamada desde la fachada que tiene los items en
2730
+ * la mano, las traduce a la anatomía de v3.
2731
+ *
2732
+ * NO-OP cuando no hay nada que traducir: un item sin `description`/`startContent`/`endContent`/
2733
+ * `classNames` se devuelve tal cual (mismo objeto), así que los 150+ items planos de las apps no
2734
+ * cambian ni un nodo.
2735
+ */
2736
+ /** Marca de lo que v2 NO pintaba en el disparador: allí solo iba el `textValue` del item. */
2737
+ const ITEM_EXTRA_ATTR = "data-aura-item-extra";
2738
+ /**
2739
+ * Clase para `Select.Value`: esconde en el disparador lo que v2 solo pintaba en la lista.
2740
+ *
2741
+ * `SelectValue` de react-aria repinta los CHILDREN del item elegido dentro del disparador
2742
+ * (`react-aria-components/dist/private/Select.mjs:226-262`). v2 pintaba ahí solo el `textValue`
2743
+ * (`@heroui/select@2.4.33`, `dist/chunk-BJWIL32D.mjs`: `state.selectedItems.map(i =>
2744
+ * i.textValue).join(", ")`), así que el icono lateral no debe colarse junto al valor. La
2745
+ * descripción se esconde SOLA: `Description` de v3 devuelve `null` sin el slot de texto
2746
+ * `description`, y `SelectValue` resetea ese contexto (`TextContext.Provider value={undefined}`,
2747
+ * misma línea) — el mismo mecanismo que usa v3 para esconder su indicador ahí (`select.css:121`).
2748
+ */
2749
+ const HIDE_ITEM_EXTRAS_CLASS = `[&_[${ITEM_EXTRA_ATTR}]]:hidden`;
2750
+ // Literales de v2 portados a la pieza de v3, sin clases v2 muertas (`text-small`/`text-tiny` no
2751
+ // existen sin el plugin `heroui()` y `cn` las borraría). El tamaño del título NO se fija: el
2752
+ // desplegable de v3 ya es `text-sm` (= el `text-small` de v2) y dentro del disparador tiene que
2753
+ // heredar el tamaño del campo (`lg` = 16px).
2754
+ const ITEM_WRAPPER_CLASS = "flex w-full min-w-0 flex-col items-start justify-center";
2755
+ const ITEM_TITLE_CLASS$1 = "min-w-0 flex-1 font-normal";
2756
+ // `Description` de v3 ya trae `text-xs text-muted` (`description.css`), y `text-muted` es el mismo
2757
+ // zinc-500/400 que el `text-foreground-500` de v2 (medido en `Dropdown.tsx`, bloque de la cabecera).
2758
+ const ITEM_DESCRIPTION_CLASS = "w-full";
2759
+ const ITEM_SIDE_CLASS = "flex shrink-0 items-center";
2760
+ // Cabecera de sección: mismo único ajuste que la fachada `Dropdown` (ver `SECTION_HEADING_CLASS`
2761
+ // en `Dropdown.tsx`: tamaño y color de `.header` ya coinciden con v2; el peso no).
2762
+ const SECTION_HEADING_CLASS$1 = "font-normal";
2763
+ // `mt-2` del slot `divider` de v2; el ancho y el color los pone el `Separator` de v3.
2764
+ const SECTION_DIVIDER_CLASS = "mt-2";
2765
+ /** `isEmpty` de v2 (`@heroui/shared-utils`): null/undefined/""/false no cuentan. */
2766
+ function hasValue$2(node) {
2767
+ return node !== null && node !== undefined && node !== "" && node !== false;
2768
+ }
2769
+ function translateItem(element) {
2770
+ const { children, className, description, startContent, endContent, classNames, } = element.props;
2771
+ const hasDescription = hasValue$2(description);
2772
+ const hasStart = hasValue$2(startContent);
2773
+ const hasEnd = hasValue$2(endContent);
2774
+ if (!(hasDescription || hasStart || hasEnd || classNames)) {
2775
+ return element;
2776
+ }
2777
+ // Solo `classNames.base` (el caso de `timezone-search-select`): nada que recomponer, basta
2778
+ // fusionar la clase en la raíz. Y children como función (render props de react-aria): no se
2779
+ // puede componer alrededor sin romper la firma, así que se aplican solo las clases de la raíz.
2780
+ const onlyBase = !(hasDescription ||
2781
+ hasStart ||
2782
+ hasEnd ||
2783
+ classNames?.title ||
2784
+ classNames?.wrapper ||
2785
+ classNames?.description);
2786
+ if (onlyBase || typeof children === "function") {
2787
+ return $8WNJc$react.cloneElement(element, {
2788
+ className: react.cn(classNames?.base, className) || undefined,
2789
+ description: undefined,
2790
+ startContent: undefined,
2791
+ endContent: undefined,
2792
+ classNames: undefined,
2793
+ });
2794
+ }
2795
+ const title = (jsxRuntime.jsx("span", { className: react.cn(ITEM_TITLE_CLASS$1, classNames?.title), children: children }));
2796
+ const next = (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [hasStart && (jsxRuntime.jsx("span", { "data-slot": "start-content", [ITEM_EXTRA_ATTR]: "", className: ITEM_SIDE_CLASS, children: startContent })), hasDescription ? (jsxRuntime.jsxs("div", { className: react.cn(ITEM_WRAPPER_CLASS, classNames?.wrapper), children: [title, jsxRuntime.jsx(react.Description, { className: react.cn(ITEM_DESCRIPTION_CLASS, classNames?.description), children: description })] })) : (title), hasEnd && (jsxRuntime.jsx("span", { "data-slot": "end-content", [ITEM_EXTRA_ATTR]: "", className: ITEM_SIDE_CLASS, children: endContent }))] }));
2797
+ return $8WNJc$react.cloneElement(element, {
2798
+ // v2: `cn(classNames?.base, className)` (`use-listbox-item.mjs`), así que `className` gana.
2799
+ className: react.cn(classNames?.base, className) || undefined,
2800
+ description: undefined,
2801
+ startContent: undefined,
2802
+ endContent: undefined,
2803
+ classNames: undefined,
2804
+ children: next,
2805
+ });
2806
+ }
2807
+ function translateSection(element) {
2808
+ const { children, className, title, showDivider, classNames } = element.props;
2809
+ const inner = withV2ItemContent(children);
2810
+ const hasTitle = hasValue$2(title);
2811
+ if (!(hasTitle || showDivider || classNames) && inner === children) {
2812
+ return element;
2813
+ }
2814
+ // ⚠️ Los hijos van como ARGUMENTOS SUELTOS de `cloneElement`, no envueltos en un Fragment:
2815
+ // `withItemIndicator` (que corre después) y `withCollectionItemIds` bajan a los items de una
2816
+ // sección leyendo `props.children` como array o elemento, y un Fragment los escondería — los
2817
+ // items de la sección se quedarían sin checkmark.
2818
+ const parts = [];
2819
+ if (hasTitle) {
2820
+ parts.push(jsxRuntime.jsx(react.Header, { className: react.cn(SECTION_HEADING_CLASS$1, classNames?.heading), children: title }, "aura-section-header"));
2821
+ }
2822
+ parts.push(...(Array.isArray(inner) ? inner : [inner]));
2823
+ if (showDivider) {
2824
+ parts.push(jsxRuntime.jsx(react.Separator, { className: react.cn(SECTION_DIVIDER_CLASS, classNames?.divider) }, "aura-section-divider"));
2825
+ }
2826
+ return $8WNJc$react.cloneElement(element, {
2827
+ className: react.cn(classNames?.base, className) || undefined,
2828
+ // `title` en v3 es el atributo global de HTML: sin retirarlo sale como *tooltip*
2829
+ // nativo sobre toda la sección (el mismo motivo por el que v2 lo quitaba del spread,
2830
+ // `chunk-Q2EVG7SO.mjs`: «removed title from props to avoid browsers showing a tooltip»).
2831
+ title: undefined,
2832
+ showDivider: undefined,
2833
+ classNames: undefined,
2834
+ }, ...parts);
2835
+ }
2836
+ /**
2837
+ * Traduce la API de contenido v2 de cada item/sección de la colección. Va DESPUÉS de
2838
+ * `withCollectionItemIds` (que deriva el `textValue` del texto del item: si la descripción ya
2839
+ * estuviera dentro, la sumaría al texto de búsqueda y del disparador) y ANTES de
2840
+ * `withItemIndicator`.
2841
+ *
2842
+ * Acepta también children como FUNCIÓN (colección dinámica con `items`, la forma de 11 de los 11
2843
+ * `AuraAutocomplete` de beweossmbs): se envuelve la función para traducir lo que devuelva.
2844
+ */
2845
+ function withV2ItemContent(children) {
2846
+ if (typeof children === "function") {
2847
+ const render = children;
2848
+ return ((item) => withV2ItemContent(render(item)));
2849
+ }
2850
+ if (Array.isArray(children)) {
2851
+ return children.map((child) => withV2ItemContent(child));
2852
+ }
2853
+ if (!$8WNJc$react.isValidElement(children)) {
2854
+ return children;
2855
+ }
2856
+ if (children.type === react.ListBoxSection) {
2857
+ return translateSection(children);
2858
+ }
2859
+ if (children.type === react.ListBoxItem) {
2860
+ return translateItem(children);
2861
+ }
2862
+ return children;
2863
+ }
2864
+
2116
2865
  /** Traducciones por defecto en español (mismo literal que la ✕ de la fachada `Input`). */
2117
2866
  const DEFAULT_TRANSLATIONS$f = {
2118
2867
  clearButtonAriaLabel: "Limpiar",
2868
+ emptyContent: "No se encontraron resultados",
2119
2869
  };
2870
+ /*
2871
+ * La lista vacía (TECH-2459, hallazgo beweossmbs 23-sep: 6 `AuraAutocomplete` pasan
2872
+ * `listboxProps.emptyContent` y ninguno se veía).
2873
+ *
2874
+ * v2 (`@heroui/autocomplete@2.3.34`, `dist/chunk-J7SBPTP2.mjs`, leído del tarball):
2875
+ *
2876
+ * allowsEmptyCollection = true, (l.36, default)
2877
+ * listboxProps: mergeProps({
2878
+ * hideEmptyContent: allowsCustomValue && !listboxProps?.emptyContent, (l.160)
2879
+ * emptyContent: "No results found.", (l.161)
2880
+ * }, listboxProps)
2881
+ *
2882
+ * O sea: abría la lista aunque el filtro la dejara vacía, y pintaba un texto — salvo con
2883
+ * `allowsCustomValue` sin texto propio (campo libre: "sin resultados" no es un error ahí). v3 invierte
2884
+ * el default (`allowsEmptyCollection` a `false`: la lista se CIERRA al quedarse vacía) y su `ListBox`
2885
+ * no pinta nada sin `renderEmptyState`. Se reponen las dos cosas con la misma regla de v2; un
2886
+ * `allowsEmptyCollection` explícito del consumidor sigue mandando. El texto por defecto va
2887
+ * traducido (patrón i18n del repo), no el inglés de v2.
2888
+ *
2889
+ * Medidas: las del estado vacío de `Select` (hueco del alto de una opción de v3, gris
2890
+ * `text-foreground-400` del slot `emptyContent` de v2), con el `px-2` del item de v3 porque este
2891
+ * popover no pisa el padding de los items como sí hace el del Select.
2892
+ */
2893
+ const EMPTY_CONTENT_CLASS$1 = "flex min-h-9 w-full items-center px-2 py-1.5 text-start text-foreground-400";
2894
+ /*
2895
+ * El mensaje de error (v2 `errorMessage`, 3 usos en beweossmbs que hoy no se veían). v2 lo pintaba
2896
+ * bajo el campo en el `helperWrapper` del `Input` (`p-1` + `text-tiny text-danger`), solo con el
2897
+ * campo inválido. `FieldError` de v3 ya trae `px-1 text-xs text-danger` (`field-error.css`) y se
2898
+ * muestra solo cuando react-aria da el campo por inválido; el `text-left` es por si el campo vive
2899
+ * en un contenedor centrado. El hueco lo da el `gap-2` del stack, en vez de los 4px del `p-1`: la
2900
+ * diferencia es de 4px y se deja en el ritmo de v3 (§9.1).
2901
+ */
2902
+ const ERROR_MESSAGE_CLASS = "text-left";
2903
+ /*
2904
+ * `isLoading` (v2): el `selectorButton` pasaba a `isLoading` y su spinner ocupaba el sitio del
2905
+ * chevron (`chunk-J7SBPTP2.mjs:168`). ⚠️ En la fachada de v2 eso no se veía con la lupa puesta,
2906
+ * porque esa fachada ocultaba el `selectorButton` cuando había icono (`selectorButton: "!hidden"`,
2907
+ * `git show b39d28f:src/components/autocomplete/AutoComplete.tsx`): el spinner se escondía con él.
2908
+ * Aquí se muestra SIEMPRE, en el sitio del extremo que haya (icono o chevron), porque una prop que
2909
+ * no pinta nada con la configuración por defecto es justo lo que la app reporta como roto.
2910
+ */
2911
+ const SPINNER = (jsxRuntime.jsx(react.Spinner, { "aria-hidden": true, "data-slot": "autocomplete-spinner", size: "sm", color: "current", className: "text-default-400" }));
2120
2912
  /**
2121
2913
  * Clases del wrapper del icono: lo mete DENTRO del recuadro del campo.
2122
2914
  *
@@ -2417,11 +3209,20 @@ const AuraAutocomplete = ({ label, id, icon, placeholder, className, children, v
2417
3209
  // limpiarlo. Se reenvían tal cual al `ComboBox`, así que para el consumidor no cambia nada:
2418
3210
  // pasar `undefined` es indistinguible de no pasarlos (react-aria decide controlado/no
2419
3211
  // controlado con `!== undefined`).
2420
- isClearable = true, isReadOnly, inputValue, selectedKey, onInputChange, onSelectionChange, translations, radius: _radius, ...props }) => {
3212
+ isClearable = true, isReadOnly, inputValue, selectedKey, onInputChange, onSelectionChange, translations, radius: _radius, isInvalid, errorMessage, listboxProps, isLoading = false, allowsCustomValue, allowsEmptyCollection, ...props }) => {
2421
3213
  const generatedId = $8WNJc$react.useId();
2422
3214
  const autoId = id || generatedId;
2423
- const resolvedIcon = icon === undefined ? DEFAULT_ICON : icon;
3215
+ // Con `isLoading` el spinner ocupa el extremo (ver `SPINNER`): a efectos de maquetación es el
3216
+ // icono, así que todas las decisiones de hueco de abajo lo tratan como tal.
3217
+ const resolvedIcon = isLoading
3218
+ ? SPINNER
3219
+ : icon === undefined
3220
+ ? DEFAULT_ICON
3221
+ : icon;
2424
3222
  const t = { ...DEFAULT_TRANSLATIONS$f, ...translations };
3223
+ // La regla de v2 para la lista vacía (ver `EMPTY_CONTENT_CLASS`).
3224
+ const hideEmptyContent = Boolean(allowsCustomValue) && listboxProps?.emptyContent == null;
3225
+ const emptyContent = listboxProps?.emptyContent ?? t.emptyContent;
2425
3226
  /** El `<input>` real, para devolverle el foco tras limpiar (ver `handleClear`). */
2426
3227
  const inputRef = $8WNJc$react.useRef(null);
2427
3228
  // `data-visible` de v2, traducido a montar/no montar: `!!state.selectedItem ||
@@ -2457,7 +3258,7 @@ isClearable = true, isReadOnly, inputValue, selectedKey, onInputChange, onSelect
2457
3258
  onInputChange?.("");
2458
3259
  inputRef.current?.focus();
2459
3260
  };
2460
- return (jsxRuntime.jsxs(react.ComboBox, { id: autoId, isDisabled: isDisabled, isReadOnly: isReadOnly, inputValue: inputValue, selectedKey: selectedKey, onInputChange: onInputChange, onSelectionChange: onSelectionChange, 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: [
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: [
2461
3262
  SIZE_CLASS$2[size],
2462
3263
  PADDING_CLASS$1[size],
2463
3264
  variant === "underlined" ? UNDERLINED_INNER_CLASS[size] : "",
@@ -2520,7 +3321,13 @@ isClearable = true, isReadOnly, inputValue, selectedKey, onInputChange, onSelect
2520
3321
  isDisabled ? ICON_DISABLED_CLASS : "",
2521
3322
  ]
2522
3323
  .filter(Boolean)
2523
- .join(" "), children: resolvedIcon })), jsxRuntime.jsx(react.ComboBox.Trigger, { className: resolvedIcon != null ? "hidden" : undefined })] }), jsxRuntime.jsx(react.ComboBox.Popover, { children: jsxRuntime.jsx(react.ListBox, { autoFocus: "first", className: OPCIONES_CLASS, children: withCollectionItemIds(children) }) })] }));
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
3325
+ ? undefined
3326
+ : () => jsxRuntime.jsx("div", { className: EMPTY_CONTENT_CLASS$1, children: emptyContent }), children: withV2ItemContent(
3327
+ // El normalizador de `utils/` está tipado solo para `ReactNode`; con una
3328
+ // función la devuelve tal cual (`isValidElement` es falso), así que el cast
3329
+ // solo alinea el tipo con lo que ya pasa en ejecución.
3330
+ withCollectionItemIds(children)) }) })] }));
2524
3331
  };
2525
3332
 
2526
3333
  /**
@@ -8279,6 +9086,32 @@ const FIELD_BORDER_CLASS = "border-2 border-default-200 bg-background focus-with
8279
9086
  */
8280
9087
  const ROOT_INLINE_LABEL_CLASS = "flex-row items-center gap-x-2 flex-nowrap";
8281
9088
  const LABEL_CLASS$2 = "text-xs font-normal text-default-600";
9089
+ /*
9090
+ * TECH-2459 · hallazgo widgets 23-sep (`findings/app-widgets-timeinput-label-inline.md`) — la
9091
+ * etiqueta ENCIMA, para el nombre `TimeInput` a secas.
9092
+ *
9093
+ * En v2 (0.6.85, `dist/index.esm.js`) había DOS componentes bajo dos nombres: la fachada de arriba
9094
+ * (con `labelPlacement: "outside-left"`) se exportaba SOLO como `TimeInputComponent`
9095
+ * (`export { …, TimeInput as TimeInputComponent, … }`), y `TimeInput` a secas llegaba por el
9096
+ * `export * from '@heroui/react'`: el crudo de HeroUI 2.7.11, con la etiqueta encima. v1 había
9097
+ * apuntado los dos nombres a la fachada en línea, y el alta de agenda de widgets (que importa
9098
+ * `TimeInput` a secas junto a un `DatePicker`) quedó con «Hora» a la izquierda y «Fecha» encima.
9099
+ *
9100
+ * Las clases son las MISMAS que la fachada de `DatePicker` pone en su raíz y en su `Label`
9101
+ * (`DatePicker.tsx`: `"flex flex-col gap-1"` y `"text-default-600 text-xs font-medium"`), para que
9102
+ * en un formulario las dos etiquetas y los dos campos caigan en la misma línea. El grupo ya mide lo
9103
+ * mismo que el `size="md"` por defecto del `DatePicker` (`h-10 min-h-10`).
9104
+ */
9105
+ const ROOT_OUTSIDE_LABEL_CLASS = "flex flex-col gap-1";
9106
+ const OUTSIDE_LABEL_CLASS = "text-default-600 text-xs font-medium";
9107
+ const ROOT_CLASS = {
9108
+ outside: ROOT_OUTSIDE_LABEL_CLASS,
9109
+ "outside-left": ROOT_INLINE_LABEL_CLASS,
9110
+ };
9111
+ const LABEL_CLASS_BY_PLACEMENT = {
9112
+ outside: OUTSIDE_LABEL_CLASS,
9113
+ "outside-left": LABEL_CLASS$2,
9114
+ };
8282
9115
  const GROUP_SIZE_CLASS = "flex-1 h-10 min-h-10 shadow-xs";
8283
9116
  /*
8284
9117
  * TECH-2459 · F5 · R3 — Avance de los segmentos: se reproduce el de v2 MEDIDO en el PNG.
@@ -8338,31 +9171,64 @@ const DAY_PERIOD_SEGMENT_CLASS = "ms-0.5";
8338
9171
  * el texto (`rgb(243,18,96)`). Deuda a reportar aparte, no de este arreglo.
8339
9172
  */
8340
9173
  const FIELD_FOCUS_CLASS$2 = "ring-0 ring-offset-0";
9174
+ /*
9175
+ * TECH-2459 · hallazgo beweossmbs 23-sep — `startContent`/`endContent` de v2, repuestas DENTRO de
9176
+ * la caja del campo.
9177
+ *
9178
+ * v2 (`@heroui/date-input@2.3.32`, `dist/time-input.js:319-329`) las pintaba en el `innerWrapper`,
9179
+ * dentro del `inputWrapper` (la caja con borde): `startContent`, los segmentos y `endContent`, en
9180
+ * ese orden. El `tv` de v2 (`@heroui/theme@2.4.15`, `date-input.js`) da a ese envoltorio
9181
+ * `flex items-center … w-full gap-x-2` y al `inputWrapper` `px-3 gap-3`. v1 no aceptaba las
9182
+ * props: el alta de citas de beweossmbs (`datetime-section.component.tsx`) pone ahí las flechas ±
9183
+ * de la rejilla de horas y desaparecían.
9184
+ *
9185
+ * En v3 el sitio equivalente son `TimeField.Prefix`/`TimeField.Suffix` (`DateInputGroupPrefix`/
9186
+ * `Suffix`), hijos del mismo `TimeField.Group`. Sus márgenes ya son los de v2 (`ms-3`/`me-3` = el
9187
+ * `px-3` del wrapper, y el input pasa a `pe-2`/`ps-2` = el `gap-x-2`), así que solo se añade una
9188
+ * cosa: `pointer-events-auto`. `.date-input-group__prefix/__suffix` de `@heroui/styles` los
9189
+ * declara `pointer-events-none` (pensados para un icono decorativo) y solo `.date-picker` los
9190
+ * reactiva; en un `TimeField` un botón puesto ahí no recibiría el clic. v2 no tenía esa regla.
9191
+ */
9192
+ const CONTENT_SLOT_CLASS = "pointer-events-auto";
8341
9193
  /**
8342
- * TimeInput genérico reutilizable, migrado al `TimeField` compound de HeroUI v3 (el `TimeInput`
8343
- * de v2 se elimina). Mantiene `label` + los props de estado de v2 (`value`, `onChange`,
8344
- * `isDisabled`, `isInvalid`, `isRequired`, `granularity`, `hourCycle`, `minValue`, `maxValue`…),
8345
- * que no cambiaron (son de React Aria, no de HeroUI).
8346
- *
8347
- * La etiqueta va en línea a la izquierda del campo, como en v2 (ver el bloque de arriba). v2 no
8348
- * exponía `labelPlacement` en su API pública (`Omit<HeroUITimeInputProps, "labelPlacement">`), así
8349
- * que aquí tampoco: la colocación es la del diseño de BeweOS y no se configura.
9194
+ * Implementación común de los dos nombres exportados. La única diferencia entre `TimeInput` y
9195
+ * `TimeInputComponent` es el valor por defecto de `labelPlacement`; una `labelPlacement` explícita
9196
+ * gana siempre.
8350
9197
  */
8351
- function TimeInput({ label, id, className,
9198
+ function TimeInputBase({ label, id, className, labelPlacement,
8352
9199
  // v2 lo forzaba a `true` en su propio hook (`@heroui/date-input@2.3.24` →
8353
9200
  // `dist/use-time-input.js:58`, `shouldForceLeadingZeros = true`), así que la referencia v2
8354
9201
  // muestra "09:00 AM"; React Aria por sí solo deja "9:00 AM". Es una prop de React Aria que v3
8355
9202
  // sigue aceptando (`AriaTimeFieldProps`), solo cambió el default.
8356
- shouldForceLeadingZeros = true, ...props }) {
9203
+ shouldForceLeadingZeros = true, startContent, endContent, ...props }) {
8357
9204
  const generatedId = $8WNJc$react.useId();
8358
9205
  const autoId = id || generatedId;
8359
9206
  return (jsxRuntime.jsxs(react.TimeField, { id: autoId,
8360
9207
  // `cn` (tailwind-merge) y no una concatenación: el consumidor tiene que poder pisar la
8361
9208
  // colocación y el ancho (`ScheduleRow` pasa `w-full`). Todas las clases de aquí son
8362
9209
  // vocabulario v3 válido, así que tailwind-merge las resuelve bien.
8363
- className: react.cn(ROOT_INLINE_LABEL_CLASS, className), shouldForceLeadingZeros: shouldForceLeadingZeros, ...props, children: [label && jsxRuntime.jsx(react.Label, { className: LABEL_CLASS$2, children: label }), jsxRuntime.jsx(react.TimeField.Group, { className: react.cn(FIELD_BORDER_CLASS, GROUP_SIZE_CLASS, FIELD_FOCUS_CLASS$2), children: jsxRuntime.jsx(react.TimeField.Input, { className: INPUT_CLASS, children: (segment) => (jsxRuntime.jsx(react.TimeField.Segment, { segment: segment, className: segment.type === "dayPeriod"
8364
- ? DAY_PERIOD_SEGMENT_CLASS
8365
- : undefined })) }) })] }));
9210
+ className: react.cn(ROOT_CLASS[labelPlacement], className), shouldForceLeadingZeros: shouldForceLeadingZeros, ...props, children: [label && (jsxRuntime.jsx(react.Label, { className: LABEL_CLASS_BY_PLACEMENT[labelPlacement], children: label })), jsxRuntime.jsxs(react.TimeField.Group, { className: react.cn(FIELD_BORDER_CLASS, GROUP_SIZE_CLASS, FIELD_FOCUS_CLASS$2), children: [startContent !== undefined && startContent !== null && (jsxRuntime.jsx(react.TimeField.Prefix, { className: CONTENT_SLOT_CLASS, children: startContent })), jsxRuntime.jsx(react.TimeField.Input, { className: INPUT_CLASS, children: (segment) => (jsxRuntime.jsx(react.TimeField.Segment, { segment: segment, className: segment.type === "dayPeriod"
9211
+ ? DAY_PERIOD_SEGMENT_CLASS
9212
+ : undefined })) }), endContent !== undefined && endContent !== null && (jsxRuntime.jsx(react.TimeField.Suffix, { className: CONTENT_SLOT_CLASS, children: endContent }))] })] }));
9213
+ }
9214
+ /**
9215
+ * `TimeInput` — el nombre que en v2 era el crudo de HeroUI: etiqueta ENCIMA del campo
9216
+ * (`labelPlacement="outside"` por defecto), alineada con la de `DatePicker`. Migrado al
9217
+ * `TimeField` compound de v3; conserva `label` + los props de estado de v2 (`value`, `onChange`,
9218
+ * `isDisabled`, `isInvalid`, `isRequired`, `granularity`, `hourCycle`, `minValue`, `maxValue`…),
9219
+ * que son de React Aria y no cambiaron: `onChange` entrega un `TimeValue` de
9220
+ * `@internationalized/date` (o `null`), igual que el crudo de v2.
9221
+ */
9222
+ function TimeInput({ labelPlacement = "outside", ...props }) {
9223
+ return jsxRuntime.jsx(TimeInputBase, { ...props, labelPlacement: labelPlacement });
9224
+ }
9225
+ /**
9226
+ * `TimeInputComponent` — la fachada BeweOS de v2: etiqueta EN LÍNEA a la izquierda del campo
9227
+ * (`labelPlacement="outside-left"` por defecto, el `outside-left` que v2 fijaba sin exponerlo).
9228
+ * Es lo que usan `ScheduleRow`, beweossmbs y chatbot-claw.
9229
+ */
9230
+ function TimeInputComponent({ labelPlacement = "outside-left", ...props }) {
9231
+ return jsxRuntime.jsx(TimeInputBase, { ...props, labelPlacement: labelPlacement });
8366
9232
  }
8367
9233
 
8368
9234
  /**
@@ -8567,7 +9433,7 @@ const ScheduleRow = ({ day, daySchedule, translations = {}, showCopyToAll = fals
8567
9433
  const isLastSlot = index === slotsToDisplay.length - 1;
8568
9434
  const isSlotFilled = slot.from && slot.to;
8569
9435
  const canDelete = slotsToDisplay.length > 1;
8570
- return (jsxRuntime.jsxs("div", { className: "flex items-center gap-1 w-full justify-between", children: [jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-1", children: [jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-1", children: [jsxRuntime.jsx(TimeInput, { label: t.from, className: "w-full", value: toTimeValue(slot.from), onChange: (value) => handleTimeChange(index, "from", value), isInvalid: !!slot.error }), jsxRuntime.jsx("span", { className: "text-default-900 xs:hidden xl:block", children: "-" }), jsxRuntime.jsx(TimeInput, { label: t.to, className: "w-full", value: toTimeValue(slot.to), onChange: (value) => handleTimeChange(index, "to", value), isInvalid: !!slot.error })] }), slot.error && (jsxRuntime.jsx("span", { className: "text-xs text-danger", children: slot.error }))] }), isLastSlot ? (jsxRuntime.jsx(Button, { isIconOnly: true, size: "sm", color: "primary", variant: "light", onPress: handleAddTimeSlot, isDisabled: !isSlotFilled, startContent: jsxRuntime.jsx(IconComponent, { icon: "solar:add-circle-bold" }) })) : (jsxRuntime.jsx(Button, { isIconOnly: true, size: "sm", color: "danger", variant: "light", onPress: () => handleRemoveTimeSlot(index), isDisabled: !canDelete, startContent: jsxRuntime.jsx(IconComponent, { icon: "solar:trash-bin-minimalistic-outline" }) }))] }, `${day}-timeslot-${index}`));
9436
+ return (jsxRuntime.jsxs("div", { className: "flex items-center gap-1 w-full justify-between", children: [jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-1", children: [jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-1", children: [jsxRuntime.jsx(TimeInputComponent, { label: t.from, className: "w-full", value: toTimeValue(slot.from), onChange: (value) => handleTimeChange(index, "from", value), isInvalid: !!slot.error }), jsxRuntime.jsx("span", { className: "text-default-900 xs:hidden xl:block", children: "-" }), jsxRuntime.jsx(TimeInputComponent, { label: t.to, className: "w-full", value: toTimeValue(slot.to), onChange: (value) => handleTimeChange(index, "to", value), isInvalid: !!slot.error })] }), slot.error && (jsxRuntime.jsx("span", { className: "text-xs text-danger", children: slot.error }))] }), isLastSlot ? (jsxRuntime.jsx(Button, { isIconOnly: true, size: "sm", color: "primary", variant: "light", onPress: handleAddTimeSlot, isDisabled: !isSlotFilled, startContent: jsxRuntime.jsx(IconComponent, { icon: "solar:add-circle-bold" }) })) : (jsxRuntime.jsx(Button, { isIconOnly: true, size: "sm", color: "danger", variant: "light", onPress: () => handleRemoveTimeSlot(index), isDisabled: !canDelete, startContent: jsxRuntime.jsx(IconComponent, { icon: "solar:trash-bin-minimalistic-outline" }) }))] }, `${day}-timeslot-${index}`));
8571
9437
  }) }))] }, day));
8572
9438
  };
8573
9439
 
@@ -8713,6 +9579,33 @@ const SIZE_TEXT_CLASS$1 = {
8713
9579
  // `min-w-0` es obligatorio: un ítem flex tiene `min-width: auto`, o sea no baja de su `min-content`,
8714
9580
  // y sin eso el texto desborda el trigger en vez de truncar con elipsis.
8715
9581
  const VALUE_ROOM_CLASS = "me-6 min-w-0 truncate";
9582
+ /*
9583
+ * `isMultiline` (v2). Literal del `tv` (`@heroui/theme@2.4.15`, `dist/components/select.js:255-263`):
9584
+ *
9585
+ * isMultiline: { true: { label: "relative", trigger: "!h-auto" }, false: { value: "truncate" } }
9586
+ *
9587
+ * O sea: el disparador deja de tener alto fijo y el valor deja de truncarse. El `min-h-*` del
9588
+ * tamaño se conserva (v2 también: el `!h-auto` solo pisaba el `h-*`), así que en una línea mide lo
9589
+ * mismo. El `py-1.5` es nuestro y es obligatorio aquí: `TRIGGER_ALIGN_CLASS` pone `py-0` porque con
9590
+ * alto fijo el centrado lo hace `items-center`; con alto automático, sin relleno vertical la
9591
+ * segunda línea tocaría el borde. 6px + 20px de línea + 6px = los 32px del `sm`, así que en una
9592
+ * sola línea no cambia nada. Van DESPUÉS en el `cn`, que resuelve `h-8`→`h-auto` y `py-0`→`py-1.5`.
9593
+ */
9594
+ const MULTILINE_TRIGGER_CLASS = "h-auto py-1.5";
9595
+ const MULTILINE_VALUE_ROOM_CLASS = "me-6 min-w-0 whitespace-normal";
9596
+ /*
9597
+ * `startContent` del disparador (v2). En v2 iba dentro del `innerWrapper`, delante del valor, con
9598
+ * el `gap-1.5` de ese slot (`select.js:100`). Aquí el disparador es el flex y el hueco va como
9599
+ * margen de la pieza, para no tocar el `gap` que v3 le pone al disparador entero.
9600
+ */
9601
+ const START_CONTENT_CLASS = "me-1.5 flex shrink-0 items-center";
9602
+ /*
9603
+ * `isLoading` (v2): el spinner SUSTITUYE al chevron en el mismo sitio. Literales de v2
9604
+ * (`@heroui/select@2.4.33`, `use-select`: `getSpinnerProps` → `color: "current"`, `size: "sm"`;
9605
+ * slot `spinner: "absolute end-3"`). Se centra en vertical como el `Select.Indicator` de v3
9606
+ * (`inset-y-0 my-auto`) y toma el color del chevron (`text-field-placeholder`, `select.css`).
9607
+ */
9608
+ const SPINNER_CLASS = "pointer-events-none absolute inset-y-0 end-3 my-auto text-field-placeholder";
8716
9609
  /*
8717
9610
  * EL ESTADO VACÍO DEL DESPLEGABLE (TECH-2459 · F5 · defecto reportado por el usuario y medido por
8718
9611
  * la sesión de backoffice: «se pulsa el campo y no pasa nada»).
@@ -9049,15 +9942,24 @@ placeholder = "", selectionMode = "single", selectedKeys, defaultSelectedKeys, o
9049
9942
  // `undefined` y el campo dejaba de llenar su contenedor: en una fila flex se encogía al ancho
9050
9943
  // de su contenido (medido por S2: 208px dentro de una fila de 592, 336px de hueco). En una
9051
9944
  // columna no se notaba porque el `stretch` del flex lo tapaba — pero afecta a cualquier app.
9052
- fullWidth = true, isOpen, defaultOpen, onOpenChange, className, classNames, translations = {}, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, "aria-details": ariaDetails, }) => {
9945
+ fullWidth = true, isLoading = false,
9946
+ // 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, }) => {
9053
9948
  const generatedId = $8WNJc$react.useId();
9054
9949
  const selectId = id || generatedId;
9055
9950
  const requiredField = isRequired;
9056
9951
  const t = { ...DEFAULT_TRANSLATIONS$8, ...translations };
9952
+ // 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;
9955
+ // Estado propio SOLO para `multiple` + `disallowEmptySelection` sin controlar: para rechazar el
9956
+ // cambio que vaciaría la selección, la fachada tiene que ser dueña del valor. En los demás casos
9957
+ // no se lee (el hook va fuera de la condición por las reglas de hooks).
9958
+ const [ownMultiple, setOwnMultiple] = $8WNJc$react.useState(() => toArray(defaultSelectedKeys) ?? []);
9057
9959
  // Mismo criterio que el `group-data-[has-helper=true]` de v2: cuenta como "con ayuda" el campo
9058
9960
  // que efectivamente PINTA una descripción o un mensaje de error, no el que solo recibe la prop.
9059
9961
  const hasHelper = Boolean((description && !isInvalid) || (isInvalid && errorMessage));
9060
- const triggerClassName = react.cn(SIZE_CLASS[size], radius === "full" ? PADDING_FULL_CLASS[size] : PADDING_CLASS[size], TRIGGER_ALIGN_CLASS, RADIUS_CLASS$4[radius], VARIANT_CLASS$1[variant],
9962
+ const triggerClassName = react.cn(SIZE_CLASS[size], radius === "full" ? PADDING_FULL_CLASS[size] : PADDING_CLASS[size], TRIGGER_ALIGN_CLASS, isMultiline ? MULTILINE_TRIGGER_CLASS : "", RADIUS_CLASS$4[radius], VARIANT_CLASS$1[variant],
9061
9963
  // En v2 el estado inválido MANDABA también con el campo enfocado
9062
9964
  // (`group-data-[focus=true]:!border-danger`), así que el borde de foco no se aplica cuando
9063
9965
  // el campo está en error: se queda el `danger`.
@@ -9070,7 +9972,32 @@ fullWidth = true, isOpen, defaultOpen, onOpenChange, className, classNames, tran
9070
9972
  "w-full", classNames?.trigger);
9071
9973
  const inner = (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [label && (jsxRuntime.jsx(react.Label, { className: react.cn(LABEL_CLASS$1, hasHelper
9072
9974
  ? LABEL_OFFSET_HELPER_CLASS[size]
9073
- : 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$1[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
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
9074
10001
  // El hueco que v2 llenaba con su `emptyContent` (ver el bloque de
9075
10002
  // `DEFAULT_TRANSLATIONS`). `renderEmptyState` es la vía de v3: el `ListBox` de
9076
10003
  // react-aria solo pinta algo con la colección vacía si se le pasa, y envuelve
@@ -9084,7 +10011,7 @@ fullWidth = true, isOpen, defaultOpen, onOpenChange, className, classNames, tran
9084
10011
  // el resultado en un `<div role="option" style="display:contents">`
9085
10012
  // (`react-aria-components/dist/private/ListBox.mjs:222-228`), o sea que la caja
9086
10013
  // que se ve es la nuestra.
9087
- renderEmptyState: () => (jsxRuntime.jsx("div", { className: EMPTY_CONTENT_CLASS, children: t.emptyContent })), children: withItemIndicator(withCollectionItemIds(children)) }) }), isInvalid && errorMessage && (jsxRuntime.jsx(react.FieldError, { className: `${HELPER_CLASS$1} text-left`, children: errorMessage }))] }));
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 }))] }));
9088
10015
  const commonProps = {
9089
10016
  id: selectId,
9090
10017
  name,
@@ -9126,7 +10053,20 @@ fullWidth = true, isOpen, defaultOpen, onOpenChange, className, classNames, tran
9126
10053
  fullWidth ? "w-full" : ROOT_MIN_WIDTH_CLASS, className),
9127
10054
  };
9128
10055
  if (selectionMode === "multiple") {
9129
- return (jsxRuntime.jsx(react.Select, { ...commonProps, selectionMode: "multiple", value: toArray(selectedKeys), defaultValue: toArray(defaultSelectedKeys), onChange: (keys) => onSelectionChange?.(new Set(keys)), children: inner }));
10056
+ // `disallowEmptySelection` en `multiple`: react-stately lo fuerza a `false` en este modo
10057
+ // (`useSelectState.mjs:61`, `disallowEmptySelection: selectionMode === 'single'`, pisando
10058
+ // la prop), así que la guarda la pone la fachada: el cambio que dejaría la selección vacía
10059
+ // no se propaga. Controlado basta con no avisar; sin controlar, la fachada lleva el valor.
10060
+ 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) => {
10062
+ if (disallowEmptySelection && keys.length === 0) {
10063
+ return;
10064
+ }
10065
+ if (ownsValue) {
10066
+ setOwnMultiple(keys);
10067
+ }
10068
+ onSelectionChange?.(new Set(keys));
10069
+ }, children: inner }));
9130
10070
  }
9131
10071
  // `Array.from` cubre las tres formas de `SelectSelection` sin ramas nuevas: `"all"` sigue
9132
10072
  // cayendo a `null` (v3 no admite "todo" en selección única) y un array se lee igual que un
@@ -9260,40 +10200,472 @@ const StatTile = ({ title, value, icon, trend, translations = {}, className, pad
9260
10200
  .join(" "), ...cardProps, children: [icon && (jsxRuntime.jsx("span", { className: "shrink-0 text-foreground-600", "aria-hidden": "true", children: icon })), jsxRuntime.jsxs("div", { className: "flex min-w-0 flex-col gap-0.5", children: [jsxRuntime.jsx(P, { className: "truncate", children: title }), jsxRuntime.jsxs("div", { className: "flex items-baseline gap-2", children: [jsxRuntime.jsx(H3, { className: "text-2xl font-semibold", children: value }), trend && (jsxRuntime.jsx("span", { className: `text-xs font-medium ${TREND_COLOR_CLASS[direction]}`, "aria-label": t[TREND_ARIA_KEY[direction]], children: trend.value }))] })] })] }));
9261
10201
  };
9262
10202
 
10203
+ // TECH-2459 — Fachada `Checkbox` (hallazgo loginos 07-sep: `index.tsx` reexportaba el `Checkbox`
10204
+ // CRUDO de v3, así que el JSX v2 de las apps — `isSelected` + `onValueChange` dentro de `Controller`
10205
+ // de RHF en backoffice ×12, smbs ×23, agency ×5 — no compilaba pese a existir el símbolo).
10206
+ //
10207
+ // HeroUI v3 convierte el Checkbox en compound (`Checkbox.Content` > `Checkbox.Control` >
10208
+ // `Checkbox.Indicator` + la etiqueta como children de `Content`), renombra `onValueChange` →
10209
+ // `onChange` y ELIMINA `color`, `size`, `radius`, `lineThrough`, `icon`, `classNames` y
10210
+ // `disableAnimation` (guía oficial `checkbox`). Su único eje propio es `variant`
10211
+ // (`primary`/`secondary`), que NO es un color: `secondary` es un tratamiento gris de menor énfasis
10212
+ // — el falso amigo de siempre (skill `auraui-design-tokens`).
10213
+ //
10214
+ // Esta fachada compone el compound por dentro y repone cada prop v2 con los literales del `tv`
10215
+ // de v2 (`@heroui/theme@2.4.15` → `dist/components/checkbox.js`), traducidos a utilidades de
10216
+ // Tailwind 4 sobre los tokens de Aurora. El relleno del estado marcado en v3 es el `::before` del
10217
+ // control (`bg-accent`, = `--color-primary` vía F1), así que `color` se reimplementa pisando ese
10218
+ // pseudo-elemento con `before:bg-<color>` — utilidad (capa `utilities`) que gana a la regla de
10219
+ // `checkbox.css` (capa `components`), incluida la de hover (`bg-accent-hover`).
10220
+ //
10221
+ // ⚠️ Estados de react-aria, verificados en el navegador contra `checkbox.css`: `data-selected`/
10222
+ // `data-indeterminate`/`data-invalid`/`data-disabled` viven en la RAÍZ (`.checkbox`);
10223
+ // `data-hovered`/`data-pressed`/`data-focus-visible` en `[data-slot="checkbox-content"]`. Por
10224
+ // eso hay dos grupos con nombre (`group/checkbox`, `group/content`) y NUNCA `data-[hover=true]`
10225
+ // (trampa 3 de la skill: v2 escribía `data-hover`; react-aria escribe `data-hovered`).
9263
10226
  /**
9264
- * Piezas que v3 monta FUERA del contenedor con scroll, como hermanas del root: el footer se queda
9265
- * a ancho completo (comentario de los autores en `table.css:15-18`), no se desplaza con la tabla.
10227
+ * Relleno del estado marcado (`::before` del control) y del indeterminado (fondo del control)
10228
+ * por color. `primary` no pisa nada: el `bg-accent` de v3 YA es `--color-primary`. `default`
10229
+ * usa el gris de Aurora (`--color-default`, = v2), nunca el `--default` crudo de v3 (D-F1-3).
10230
+ *
10231
+ * Literal v2: `after:bg-<color> after:text-<color>-foreground text-<color>-foreground`.
9266
10232
  */
9267
- const OUTSIDE_SCROLL = new Set([react.Table.Footer]);
10233
+ const CONTROL_COLOR = {
10234
+ primary: "",
10235
+ default: "before:bg-default group-data-[indeterminate=true]/checkbox:bg-default",
10236
+ secondary: "before:bg-secondary group-data-[indeterminate=true]/checkbox:bg-secondary",
10237
+ success: "before:bg-success group-data-[indeterminate=true]/checkbox:bg-success",
10238
+ warning: "before:bg-warning group-data-[indeterminate=true]/checkbox:bg-warning",
10239
+ danger: "before:bg-danger group-data-[indeterminate=true]/checkbox:bg-danger",
10240
+ };
9268
10241
  /**
9269
- * Piezas que YA son un contenedor con scroll propio (`.table__scroll-container` /
9270
- * `.table__resizable-container`, ambas con `overflow` en `table.css`): si el consumidor monta una,
9271
- * la fachada no envuelve nada — anidar dos contenedores de scroll dejaría el interior sin ancho
9272
- * que desbordar.
10242
+ * Color del glifo (check / línea). v3 lo fija en el SVG (`[data-slot="checkbox-default-indicator--
10243
+ * checkmark"] { text-accent-foreground }`), así que hay que alcanzar al SVG, no al indicador.
9273
10244
  */
9274
- const OWN_SCROLL = new Set([
9275
- react.Table.ScrollContainer,
9276
- react.Table.ResizableContainer,
9277
- ]);
9278
- /** Identidad por `child.type` contra los objetos reales de HeroUI (mismo patrón que la fachada `Alert`). */
9279
- function isPart$2(node, parts) {
9280
- return $8WNJc$react.isValidElement(node) && parts.has(node.type);
10245
+ const INDICATOR_COLOR = {
10246
+ primary: "",
10247
+ default: "[&_svg]:text-default-foreground",
10248
+ secondary: "[&_svg]:text-secondary-foreground",
10249
+ success: "[&_svg]:text-success-foreground",
10250
+ warning: "[&_svg]:text-warning-foreground",
10251
+ danger: "[&_svg]:text-danger-foreground",
10252
+ };
10253
+ // v2 (`checkbox.js`, variante `size`): wrapper `w-4/w-5/w-6` (16/20/24px), label
10254
+ // `text-small/medium/large`, icon `w-3 h-2` / `w-4 h-3` / `w-5 h-4`. v3: control `size-4`, glifo
10255
+ // `size-2.5`, texto `text-sm`, para todos.
10256
+ const CONTROL_SIZE = {
10257
+ sm: "size-4",
10258
+ md: "size-5",
10259
+ lg: "size-6",
10260
+ };
10261
+ const INDICATOR_SIZE = {
10262
+ sm: "size-3 [&_svg]:size-2.5",
10263
+ md: "size-3.5 [&_svg]:size-3",
10264
+ lg: "size-4 [&_svg]:size-3.5",
10265
+ };
10266
+ const LABEL_SIZE = {
10267
+ sm: "text-sm",
10268
+ md: "text-base",
10269
+ lg: "text-lg",
10270
+ };
10271
+ // v2: `rounded-[calc(theme(borderRadius.medium)*K)]` con `medium` = 12px → 6 / 7.2 / 8.4 px, y por
10272
+ // defecto el radio lo decidía `size` (sm→0.5, md→0.6, lg→0.7). El `::before` (relleno) lleva el
10273
+ // mismo radio porque aquí cubre el border-box completo (ver `CONTROL_BASE`).
10274
+ const RADIUS_CLASS$3 = {
10275
+ none: "rounded-none before:rounded-none",
10276
+ sm: "rounded-[6px] before:rounded-[6px]",
10277
+ md: "rounded-[7.2px] before:rounded-[7.2px]",
10278
+ lg: "rounded-[8.4px] before:rounded-[8.4px]",
10279
+ full: "rounded-full before:rounded-full",
10280
+ };
10281
+ const RADIUS_BY_SIZE = {
10282
+ sm: "sm",
10283
+ md: "md",
10284
+ lg: "lg",
10285
+ };
10286
+ /**
10287
+ * Caja del control (slot `wrapper` de v2): borde de 2px en `--color-default` sin sombra
10288
+ * (`before:border-2 before:border-default`), y el relleno cubriendo la caja ENTERA al marcarse.
10289
+ *
10290
+ * En v3 el borde vive en el propio control (1px, `border-field-border`, `shadow-field`) y el
10291
+ * relleno es un `::before` a `inset-0`, que solo alcanza al padding-box: por eso v3 pone
10292
+ * `border-transparent` al marcarse y el relleno queda 1px por dentro. Con el borde de 2px de v2
10293
+ * eso dejaría un anillo de 2px: se lleva el `::before` al border-box (`-inset-0.5` = −2px) y se
10294
+ * quita el `overflow-hidden` de v3 para que no lo recorte. El `::before` es un descendiente
10295
+ * posicionado, así que pinta ENCIMA del borde y lo tapa, como el `after` de v2.
10296
+ *
10297
+ * Hover v2: `group-data-[hover=true]:before:bg-default-100` — tinte suave de la caja sin marcar
10298
+ * (en v3 la caja es el fondo del control, no un pseudo). `data-hovered` lo emite `Checkbox.Content`.
10299
+ *
10300
+ * Indeterminado: v3 pinta el fondo del control (dentro del borde). Se enciende también el
10301
+ * `::before` para que el relleno cubra la caja entera, igual que al marcar.
10302
+ */
10303
+ const CONTROL_BASE = [
10304
+ "border-2 border-default shadow-none overflow-visible",
10305
+ "before:-inset-0.5",
10306
+ "group-data-[hovered=true]/content:bg-default-100",
10307
+ "group-data-[indeterminate=true]/checkbox:before:scale-100",
10308
+ "group-data-[indeterminate=true]/checkbox:before:opacity-100",
10309
+ ].join(" ");
10310
+ // v2 (`lineThrough: true`, slot `label`): la raya crece de 0 al 100% al marcarse y la etiqueta se
10311
+ // atenúa al 60%. `data-selected` está en la raíz → `group-data-[…]/checkbox`.
10312
+ const LINE_THROUGH = [
10313
+ "inline-flex items-center justify-center",
10314
+ "before:absolute before:h-0.5 before:w-0 before:bg-foreground before:content-['']",
10315
+ "group-data-[selected=true]/checkbox:opacity-60",
10316
+ "group-data-[selected=true]/checkbox:before:w-full",
10317
+ ].join(" ");
10318
+ // Los subcomponentes compound de v3, adjuntos como estáticos para que la API v3 siga funcionando
10319
+ // a través de la fachada (`<Checkbox><Checkbox.Control>…`).
10320
+ const COMPOUND_PARTS$1 = new Set([
10321
+ react.Checkbox.Control,
10322
+ react.Checkbox.Indicator,
10323
+ react.Checkbox.Content,
10324
+ ]);
10325
+ /** API compound de v3 detectada por hijos directos: la fachada no envuelve nada. */
10326
+ function hasCompoundChildren$1(children) {
10327
+ return $8WNJc$react.Children.toArray(children).some((child) => $8WNJc$react.isValidElement(child) && COMPOUND_PARTS$1.has(child.type));
9281
10328
  }
9282
- const TableFacade = ({ children, ...props }) => {
10329
+ /**
10330
+ * Checkbox - Casilla basada en HeroUI v3 con la API pública de v2 intacta (TECH-2459).
10331
+ *
10332
+ * - **API v2**: `children` como etiqueta, `isSelected`/`defaultSelected` + `onValueChange`
10333
+ * (también `onChange`), `color`, `size`, `radius`, `lineThrough`, `icon`, `classNames`. Funciona
10334
+ * dentro de `Controller` de react-hook-form (`isSelected={value} onValueChange={onChange}`).
10335
+ * - **API compound v3**: si los children traen `Checkbox.Control`/`Indicator`/`Content`, pasa tal
10336
+ * cual a la raíz de HeroUI (con `onValueChange` seguido igualmente).
10337
+ */
10338
+ const CheckboxFacade = ({ children, onChange, onValueChange, color = "primary", size = "md", radius, lineThrough = false, icon, disableAnimation = false, isInvalid, classNames, className, ...rest }) => {
10339
+ const handleChange = (isSelected) => {
10340
+ onChange?.(isSelected);
10341
+ onValueChange?.(isSelected);
10342
+ };
10343
+ if (hasCompoundChildren$1(children)) {
10344
+ return (jsxRuntime.jsx(react.Checkbox, { ...rest, isInvalid: isInvalid, onChange: handleChange, className: react.cn(classNames?.base, className), children: children }));
10345
+ }
10346
+ const indicatorContent = typeof icon === "function"
10347
+ ? (state) => icon({
10348
+ isSelected: state.isSelected,
10349
+ isIndeterminate: state.isIndeterminate,
10350
+ isDisabled: state.isDisabled,
10351
+ })
10352
+ : icon;
10353
+ const hasLabel = children !== undefined && children !== null && children !== false;
10354
+ return (jsxRuntime.jsx(react.Checkbox, { ...rest, isInvalid: isInvalid, onChange: handleChange,
10355
+ // Base v2: `group relative max-w-fit inline-flex items-center justify-start cursor-pointer
10356
+ // p-2 -m-2 select-none` (el `p-2 -m-2` es el área de pulsado ampliada de v2). v3
10357
+ // (`.checkbox`) usa `flex flex-col items-start` para apilar `Description`/`FieldError`,
10358
+ // slots que esta fachada no renderiza; en **inline** la caja participa de la línea de
10359
+ // texto, donde la esperan los consumidores (mismo criterio que la fachada `Switch`).
10360
+ className: react.cn("group/checkbox relative -m-2 inline-flex max-w-fit flex-row items-center p-2", classNames?.base, className), children: jsxRuntime.jsxs(react.Checkbox.Content, { className: "group/content gap-2 font-normal", children: [jsxRuntime.jsx(react.Checkbox.Control, { className: react.cn(CONTROL_BASE, CONTROL_SIZE[size], RADIUS_CLASS$3[radius ?? RADIUS_BY_SIZE[size]], CONTROL_COLOR[color],
10361
+ // v2 `isInvalid`: `before:border-danger` (el borde vivía en el pseudo).
10362
+ isInvalid && "border-danger", disableAnimation && "transition-none before:transition-none", classNames?.wrapper), children: jsxRuntime.jsx(react.Checkbox.Indicator, { className: react.cn(INDICATOR_SIZE[size], INDICATOR_COLOR[color], disableAnimation && "[&_svg]:transition-none", classNames?.icon), children: indicatorContent }) }), hasLabel && (
10363
+ // Slot `label` de v2: `relative text-foreground select-none` + tamaño de texto.
10364
+ // `data-slot="label"` para que las reglas de cursor/selección de `.checkbox` de v3
10365
+ // le alcancen igual que a un `<Label>`.
10366
+ jsxRuntime.jsx("span", { "data-slot": "label", className: react.cn("relative select-none text-foreground", LABEL_SIZE[size], lineThrough && LINE_THROUGH, isInvalid && "text-danger", classNames?.label), children: children }))] }) }));
10367
+ };
10368
+ CheckboxFacade.displayName = "Aurora.Checkbox";
10369
+ /**
10370
+ * `Checkbox` de Aurora: fachada v2 + estáticos compound de v3 (`Checkbox.Control`,
10371
+ * `Checkbox.Indicator`, `Checkbox.Content`), los mismos objetos que exporta `@heroui/react`.
10372
+ */
10373
+ const Checkbox = Object.assign(CheckboxFacade, {
10374
+ Control: react.Checkbox.Control,
10375
+ Indicator: react.Checkbox.Indicator,
10376
+ Content: react.Checkbox.Content,
10377
+ });
10378
+
10379
+ // TECH-2459 — Piezas que la fachada `Table.Content` inserta para reponer la API v2 que v3 dejó en
10380
+ // manos de la app (defectos reportados por beweossmbs, 23-sep). Privadas: NO se reexportan desde
10381
+ // `_internal/index.ts` ni desde el `index.ts` de la carpeta.
10382
+ //
10383
+ // Todo se decide por IDENTIDAD de `child.type` contra los objetos reales de HeroUI (mismo patrón
10384
+ // que las fachadas `Alert`, `Tabs` y el propio root de `Table`), nunca por `displayName`.
10385
+ /**
10386
+ * `isHeaderSticky` declarado en el ROOT (`<Table isHeaderSticky>`, que es donde lo ponía v2) viaja
10387
+ * hasta `Table.Content` por contexto: el `<thead>` lo pinta el contenido, no el root.
10388
+ */
10389
+ const TableStickyContext = $8WNJc$react.createContext(undefined);
10390
+ /**
10391
+ * Clases de v2 para `isHeaderSticky` (`@heroui/theme@2.4.15` → `dist/components/table.js`,
10392
+ * variante `isHeaderSticky.true.thead`): `sticky top-0 z-20 [&>tr]:first:shadow-small`. La sombra
10393
+ * se descarta: `shadow-small` es una clase del plugin de v2 que en Tailwind 4 no genera nada, y
10394
+ * v3 separa la cabecera del cuerpo con su propio borde (`.table__header { border-b }`).
10395
+ *
10396
+ * El «contenedor con scroll» respecto al que se pega es el `Table.ScrollContainer` que inserta la
10397
+ * fachada del root (`overflow-x: auto` ⇒ `overflow-y` computa `auto`), exactamente como en v2 lo
10398
+ * era el `wrapper` (`overflow-auto`): la cabecera se queda fija cuando ese contenedor tiene alto
10399
+ * acotado y desplaza en vertical.
10400
+ */
10401
+ const STICKY_HEADER_CLASS = "sticky top-0 z-20";
10402
+ /** Clave fija de la columna/celda de selección insertadas (no colisiona con claves de negocio). */
10403
+ const SELECTION_KEY = "__aurora-selection";
10404
+ /**
10405
+ * Columna de «seleccionar todo». El `Checkbox slot="selection"` recibe de react-aria, por
10406
+ * `CheckboxContext`, el estado de la tabla: marcado con todo seleccionado, INDETERMINADO con una
10407
+ * selección parcial, y su `onChange` selecciona/deselecciona todo — `onSelectionChange` recibe
10408
+ * `"all"`, igual que en v2 (`useTableSelectAllCheckbox`). La etiqueta accesible también la pone
10409
+ * react-aria, traducida a la locale activa: por eso esta pieza no tiene texto propio ni
10410
+ * `translations`.
10411
+ *
10412
+ * `pe-0` es lo que hace la anatomía oficial de v3 con su columna de selección: la casilla no
10413
+ * necesita el padding final de una columna de texto.
10414
+ */
10415
+ function SelectionColumn() {
10416
+ return (jsxRuntime.jsx(react.Table.Column, { className: "pe-0", id: SELECTION_KEY, children: jsxRuntime.jsx(Checkbox, { slot: "selection" }) }));
10417
+ }
10418
+ /** Casilla por fila (`useTableSelectionCheckbox` vía `CheckboxContext`, como en v2). */
10419
+ function SelectionCell() {
10420
+ return (jsxRuntime.jsx(react.Table.Cell, { className: "pe-0", children: jsxRuntime.jsx(Checkbox, { slot: "selection" }) }));
10421
+ }
10422
+ function isElementOf(node, type) {
10423
+ return $8WNJc$react.isValidElement(node) && node.type === type;
10424
+ }
10425
+ /**
10426
+ * Hijos listos para pasarse como argumentos VARIÁDICOS de `cloneElement`/`createElement`. Un array
10427
+ * pasado como argumento único es para React una LISTA y exige `key` en cada elemento: si la app
10428
+ * escribió hijos JSX sueltos (sin clave, legal) y la fachada los reenviara como un solo array,
10429
+ * saltaría «Each child in a list should have a unique key» (medido en jsdom). Esparcidos, React
10430
+ * los trata como los trató al escribirlos.
10431
+ */
10432
+ function spread(node) {
10433
+ return Array.isArray(node) ? node : [node];
10434
+ }
10435
+ /**
10436
+ * Aplica `fn` a cada nodo de un árbol de `children` atravesando arrays y Fragments, sin tocar
10437
+ * claves (`cloneElement` las conserva). `Children.map` no sirve aquí: re-prefija las claves y NO
10438
+ * entra en Fragments — y las apps envuelven en Fragment cuando el contenido es condicional.
10439
+ */
10440
+ function mapNodes(node, fn) {
10441
+ if (Array.isArray(node)) {
10442
+ return node.map((child) => mapNodes(child, fn));
10443
+ }
10444
+ if (isElementOf(node, $8WNJc$react.Fragment)) {
10445
+ return $8WNJc$react.cloneElement(node, undefined, ...spread(mapNodes(node.props.children, fn)));
10446
+ }
10447
+ return fn(node);
10448
+ }
10449
+ /**
10450
+ * ¿Hay ya una casilla de selección montada (por la fachada o por la app) en este subárbol
10451
+ * estático? Mira la identidad de las piezas propias y cualquier elemento con `slot="selection"`
10452
+ * (la forma que documenta v3). Las funciones de render no se pueden inspeccionar sin llamarlas:
10453
+ * esas cuentan como «no hay».
10454
+ */
10455
+ function hasSelectionControl(node) {
10456
+ if (Array.isArray(node)) {
10457
+ return node.some(hasSelectionControl);
10458
+ }
10459
+ if (!$8WNJc$react.isValidElement(node)) {
10460
+ return false;
10461
+ }
10462
+ if (node.type === SelectionColumn || node.type === SelectionCell) {
10463
+ return true;
10464
+ }
10465
+ const props = node.props;
10466
+ if (props.slot === "selection") {
10467
+ return true;
10468
+ }
10469
+ return typeof props.children === "function"
10470
+ ? false
10471
+ : hasSelectionControl(props.children);
10472
+ }
10473
+ function containsType(node, type) {
10474
+ if (Array.isArray(node)) {
10475
+ return node.some((child) => containsType(child, type));
10476
+ }
10477
+ if (!$8WNJc$react.isValidElement(node)) {
10478
+ return false;
10479
+ }
10480
+ if (node.type === type) {
10481
+ return true;
10482
+ }
10483
+ const children = node.props.children;
10484
+ return typeof children === "function" ? false : containsType(children, type);
10485
+ }
10486
+ /**
10487
+ * Columna con `allowsSorting`: v2 pintaba el chevron por su cuenta (`TableColumnHeader`,
10488
+ * `ChevronDownIcon` con `data-direction`); v3 no pinta nada y pide que la app monte
10489
+ * `Table.SortableColumnHeader` en el render prop de la columna. Aquí se monta esa pieza de v3
10490
+ * — aspecto de v3 (§9.1): chevron a la derecha que apunta arriba en ascendente y gira en
10491
+ * descendente, y sin chevron mientras la columna no es la ordenada.
10492
+ *
10493
+ * No se toca la columna si la app ya decide el contenido con un render prop (entonces es suyo) o
10494
+ * si ya montó un `SortableColumnHeader` estático.
10495
+ */
10496
+ function withSortIndicator(node) {
10497
+ if (!isElementOf(node, react.Table.Column)) {
10498
+ return node;
10499
+ }
10500
+ const { allowsSorting, children } = node.props;
10501
+ if (!allowsSorting ||
10502
+ typeof children === "function" ||
10503
+ containsType(children, react.Table.SortableColumnHeader)) {
10504
+ return node;
10505
+ }
10506
+ return $8WNJc$react.cloneElement(node, {
10507
+ children: ({ sortDirection }) => (jsxRuntime.jsx(react.Table.SortableColumnHeader, { sortDirection: sortDirection, children: children })),
10508
+ });
10509
+ }
10510
+ /** Compone una clase sobre un `className` de react-aria, que puede ser string o render prop. */
10511
+ function withClass(className, extra) {
10512
+ if (typeof className === "function") {
10513
+ return (renderProps) => react.cn(className(renderProps), extra);
10514
+ }
10515
+ return react.cn(className, extra);
10516
+ }
10517
+ /**
10518
+ * `Table.Header`: indicador de orden en cada columna ordenable, clase sticky si se pidió y la
10519
+ * columna de selección delante si toca.
10520
+ *
10521
+ * Con columnas dinámicas (`columns` + función) la columna de selección no se puede colar en el
10522
+ * array de la app — su función de render la recibiría como si fuera una columna de negocio. Se usa
10523
+ * la forma que documenta react-aria para mezclar estáticas y dinámicas: la estática delante y las
10524
+ * de la app en un `Table.Collection` con sus `items`. El `Collection` exterior del header hereda
10525
+ * `dependencies` por contexto (`CollectionBuilder`), así que no se pierden.
10526
+ */
10527
+ function transformHeader(header, options) {
10528
+ const { children, columns, className } = header.props;
10529
+ const props = options.sticky
10530
+ ? { className: withClass(className, STICKY_HEADER_CLASS) }
10531
+ : {};
10532
+ if (typeof children === "function") {
10533
+ const render = children;
10534
+ const wrapped = (column) => withSortIndicator(render(column));
10535
+ if (!options.insertSelection) {
10536
+ return $8WNJc$react.cloneElement(header, { ...props, children: wrapped });
10537
+ }
10538
+ return $8WNJc$react.cloneElement(header, { ...props, columns: undefined }, jsxRuntime.jsx(SelectionColumn, {}, SELECTION_KEY), jsxRuntime.jsx(react.Table.Collection, { items: columns, children: wrapped }, "columns"));
10539
+ }
10540
+ const mapped = spread(mapNodes(children, withSortIndicator));
10541
+ if (!options.insertSelection) {
10542
+ return $8WNJc$react.cloneElement(header, props, ...mapped);
10543
+ }
10544
+ return $8WNJc$react.cloneElement(header, props, jsxRuntime.jsx(SelectionColumn, {}, SELECTION_KEY), ...mapped);
10545
+ }
10546
+ /**
10547
+ * Casilla delante de las celdas de una fila. Solo sabe tratar `Table.Row` (el mismo objeto que el
10548
+ * `TableRow` suelto): una fila envuelta en un componente propio de la app no se puede abrir, y se
10549
+ * devuelve tal cual (límite documentado en el README).
10550
+ */
10551
+ function withSelectionCell(row) {
10552
+ if (!isElementOf(row, react.Table.Row)) {
10553
+ return row;
10554
+ }
10555
+ const { children, columns } = row.props;
10556
+ if (typeof children === "function") {
10557
+ // Mismo mecanismo que el header: estática + `Collection` con las columnas de la app. El
10558
+ // `idScope` de la fila (su `id`) lo hereda la colección interior por contexto, así que las
10559
+ // claves de las celdas siguen siendo únicas por fila.
10560
+ return $8WNJc$react.cloneElement(row, { columns: undefined }, jsxRuntime.jsx(SelectionCell, {}, SELECTION_KEY), jsxRuntime.jsx(react.Table.Collection, { items: columns, children: children }, "cells"));
10561
+ }
10562
+ if (hasSelectionControl(children)) {
10563
+ return row;
10564
+ }
10565
+ return $8WNJc$react.cloneElement(row, undefined, jsxRuntime.jsx(SelectionCell, {}, SELECTION_KEY), ...spread(children));
10566
+ }
10567
+ /** `Table.Body`: una casilla por fila, tanto con `items` + función como con filas estáticas. */
10568
+ function transformBody(body) {
10569
+ const { children } = body.props;
10570
+ if (typeof children === "function") {
10571
+ const render = children;
10572
+ return $8WNJc$react.cloneElement(body, {
10573
+ children: (item) => withSelectionCell(render(item)),
10574
+ });
10575
+ }
10576
+ return $8WNJc$react.cloneElement(body, undefined, ...spread(mapNodes(children, withSelectionCell)));
10577
+ }
10578
+
10579
+ /** Default de v2 (`use-table`, `@heroui/table@2.2.32`), sin reinterpretar. */
10580
+ function defaultShowCheckboxes(props) {
10581
+ const behavior = props.selectionBehavior ?? "toggle";
10582
+ return props.selectionMode === "multiple" && behavior !== "replace";
10583
+ }
10584
+ /** Primer `Table.Header` del árbol (atravesando arrays y Fragments), para decidir la selección. */
10585
+ function findHeader(node) {
10586
+ let found;
10587
+ mapNodes(node, (child) => {
10588
+ if (!found && isElementOf(child, react.Table.Header)) {
10589
+ found = child;
10590
+ }
10591
+ return child;
10592
+ });
10593
+ return found;
10594
+ }
10595
+ /**
10596
+ * `Table.Content` de Aurora: el `Table.Content` de HeroUI v3 con las casillas de selección, el
10597
+ * indicador de orden y la cabecera fija de v2. Idempotente: si la app ya montó sus casillas
10598
+ * (`slot="selection"`) o su `SortableColumnHeader`, no se inserta nada encima.
10599
+ */
10600
+ function TableContent({ children, isHeaderSticky, showSelectionCheckboxes, ...props }) {
10601
+ const rootSticky = $8WNJc$react.useContext(TableStickyContext);
10602
+ const sticky = isHeaderSticky ?? rootSticky ?? false;
10603
+ const header = findHeader(children);
10604
+ // La decisión es de TABLA, no de fila: si la cabecera ya trae casilla (la montó la app), no
10605
+ // se inserta ni en la cabecera ni en las filas — insertar solo en unas descuadraría el número
10606
+ // de celdas y react-aria lanza `Cell count must match column count`.
10607
+ const insertSelection = (showSelectionCheckboxes ?? defaultShowCheckboxes(props)) &&
10608
+ header !== undefined &&
10609
+ !(typeof header.props.children !== "function" &&
10610
+ hasSelectionControl(header.props.children));
10611
+ const content = mapNodes(children, (child) => {
10612
+ if (isElementOf(child, react.Table.Header)) {
10613
+ return transformHeader(child, { insertSelection, sticky });
10614
+ }
10615
+ if (insertSelection && isElementOf(child, react.Table.Body)) {
10616
+ return transformBody(child);
10617
+ }
10618
+ return child;
10619
+ });
10620
+ // 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));
10622
+ }
10623
+ TableContent.displayName = "Aurora.Table.Content";
10624
+
10625
+ /**
10626
+ * Piezas que v3 monta FUERA del contenedor con scroll, como hermanas del root: el footer se queda
10627
+ * a ancho completo (comentario de los autores en `table.css:15-18`), no se desplaza con la tabla.
10628
+ */
10629
+ const OUTSIDE_SCROLL = new Set([react.Table.Footer]);
10630
+ /**
10631
+ * Piezas que YA son un contenedor con scroll propio (`.table__scroll-container` /
10632
+ * `.table__resizable-container`, ambas con `overflow` en `table.css`): si el consumidor monta una,
10633
+ * la fachada no envuelve nada — anidar dos contenedores de scroll dejaría el interior sin ancho
10634
+ * que desbordar.
10635
+ */
10636
+ const OWN_SCROLL = new Set([
10637
+ react.Table.ScrollContainer,
10638
+ react.Table.ResizableContainer,
10639
+ ]);
10640
+ /** Identidad por `child.type` contra los objetos reales de HeroUI (mismo patrón que la fachada `Alert`). */
10641
+ function isPart$2(node, parts) {
10642
+ return $8WNJc$react.isValidElement(node) && parts.has(node.type);
10643
+ }
10644
+ /**
10645
+ * `isHeaderSticky` del root viaja a `Table.Content` por contexto (el `<thead>` lo pinta el
10646
+ * contenido). Solo se monta el provider cuando la app declara la prop: sin ella el árbol del root
10647
+ * queda exactamente como antes (`.Provider` y no `<Context value>` porque el peer admite React 18).
10648
+ */
10649
+ function withSticky(isHeaderSticky, root) {
10650
+ return isHeaderSticky === undefined ? (root) : (jsxRuntime.jsx(TableStickyContext.Provider, { value: isHeaderSticky, children: root }));
10651
+ }
10652
+ const TableFacade = ({ children, isHeaderSticky, ...props }) => {
9283
10653
  const nodes = $8WNJc$react.Children.toArray(children);
9284
10654
  // Sin hijos, o con un contenedor de scroll ya montado a mano: el root tal cual.
9285
10655
  if (nodes.length === 0 || nodes.some((node) => isPart$2(node, OWN_SCROLL))) {
9286
- return jsxRuntime.jsx(react.Table, { ...props, children: children });
10656
+ return withSticky(isHeaderSticky, jsxRuntime.jsx(react.Table, { ...props, children: children }));
9287
10657
  }
9288
10658
  const scrollable = nodes.filter((node) => !isPart$2(node, OUTSIDE_SCROLL));
9289
10659
  const outsideScroll = nodes.filter((node) => isPart$2(node, OUTSIDE_SCROLL));
9290
- return (jsxRuntime.jsxs(react.Table, { ...props, children: [jsxRuntime.jsx(react.Table.ScrollContainer, { children: scrollable }), outsideScroll] }));
10660
+ return withSticky(isHeaderSticky, jsxRuntime.jsxs(react.Table, { ...props, children: [jsxRuntime.jsx(react.Table.ScrollContainer, { children: scrollable }), outsideScroll] }));
9291
10661
  };
9292
10662
  TableFacade.displayName = "Aurora.Table";
9293
10663
  /**
9294
10664
  * `Table` de Aurora: el root de HeroUI v3 con el `ScrollContainer` que faltaba, más los estáticos
9295
- * del compound de v3 — los MISMOS objetos que exporta `@heroui/react`, así que
9296
- * `Table.Content`/`Table.Header`/`isRowHeader`… se comportan igual que antes.
10665
+ * del compound de v3 — los MISMOS objetos que exporta `@heroui/react` (así que
10666
+ * `Table.Header`/`Table.Column isRowHeader`… se comportan igual que antes), SALVO `Table.Content`,
10667
+ * que es la fachada de Aurora: inserta las casillas de selección múltiple, el indicador de orden
10668
+ * de `allowsSorting` y la cabecera fija de `isHeaderSticky` (ver `TableContent.tsx`).
9297
10669
  *
9298
10670
  * `Table.Root` queda deliberadamente crudo (el root pelado de v3, sin scroll): es la vía de escape
9299
10671
  * para quien quiera montar la anatomía entera a mano.
@@ -9304,7 +10676,7 @@ const Table = Object.assign(TableFacade, {
9304
10676
  Collection: react.Table.Collection,
9305
10677
  Column: react.Table.Column,
9306
10678
  ColumnResizer: react.Table.ColumnResizer,
9307
- Content: react.Table.Content,
10679
+ Content: TableContent,
9308
10680
  Footer: react.Table.Footer,
9309
10681
  Header: react.Table.Header,
9310
10682
  LoadMore: react.Table.LoadMore,
@@ -9373,6 +10745,225 @@ function AuraTable({ columns, items, renderCell, children, className, ...props }
9373
10745
  return (jsxRuntime.jsx(Table, { className: `rounded-none ${className ?? ""}`.trim(), children: jsxRuntime.jsxs(Table.Content, { ...props, children: [jsxRuntime.jsx(Table.Header, { columns: columns, children: (column) => (jsxRuntime.jsx(Table.Column, { isRowHeader: column.isRowHeader || column.key === rowHeaderKey, children: column.label }, column.key)) }), jsxRuntime.jsx(Table.Body, { items: items, children: (item) => (jsxRuntime.jsx(Table.Row, { columns: columns, children: (column) => (jsxRuntime.jsx(Table.Cell, { children: renderCell ? renderCell(item, column.key) : children })) }, item.id)) })] }) }));
9374
10746
  }
9375
10747
 
10748
+ // ── Autocrecimiento entre `minRows` y `maxRows` (reposición de v2) ─────────────────────────────
10749
+ //
10750
+ // v2 montaba `react-textarea-autosize@8.5.9` (`@heroui/input@2.4.22`, `dist/chunk-RIE4DNUQ.mjs`)
10751
+ // y aquí esa dependencia no existe, así que se reimplementa su algoritmo, leído en su compilado
10752
+ // (`dist/react-textarea-autosize.browser.esm.js`, `calculateNodeHeight` + `resizeTextarea`):
10753
+ //
10754
+ // 1. Se copia el estilo que decide el ajuste de línea (fuente, relleno, borde, ancho…) a un
10755
+ // `<textarea>` OCULTO compartido, se le pone el valor (o el placeholder, o "x") y se lee su
10756
+ // `scrollHeight`. Se mide en un clon y no en el nodo real porque encoger el real a 0 para
10757
+ // medirlo mueve el scroll de la página cuando el campo está cerca del borde inferior.
10758
+ // 2. El alto de UNA línea sale del mismo clon con el valor "x", no de una constante: así cuadra
10759
+ // con la fuente y el tamaño que el campo tenga de verdad.
10760
+ // 3. `alto = min(máx, max(mín, contenido))`, con mín/máx = filas × línea + relleno (+ borde en
10761
+ // `border-box`). Si `maxRows < minRows`, gana `maxRows` — el `min` va por fuera, como en v2.
10762
+ // 4. Se escribe con `setProperty("height", …, "important")`, literal de v2.
10763
+ //
10764
+ // Cuándo se recalcula (los mismos disparadores que v2, más el ancho):
10765
+ // · en cada render de la fachada (layout effect sin dependencias, como v2) → cubre el valor
10766
+ // CONTROLADO y cualquier cambio de `size`/`className` que mueva la fuente o el ancho;
10767
+ // · al escribir, en los dos modos (`resize` desde el `onChange` de la fachada). Hace falta
10768
+ // aunque el valor viva en estado de React: en modo NO controlado ese estado es del
10769
+ // `TextField` de react-aria, y su re-render no re-renderiza la fachada, así que el layout
10770
+ // effect de arriba no corre;
10771
+ // · al cambiar el ANCHO del campo (`ResizeObserver`; v2 solo escuchaba `window.resize`, que no
10772
+ // ve un panel que se estrecha) — solo si cambió el ancho, para no reaccionar a nuestro propio
10773
+ // cambio de alto;
10774
+ // · al terminar de cargar una fuente web (`document.fonts` → `loadingdone`, como v2);
10775
+ // · tras el `reset` del formulario (como v2): `useFormReset` de react-aria devuelve el estado del
10776
+ // `TextField` al `defaultValue`, otra vez sin re-renderizar la fachada. Se mide en el frame
10777
+ // siguiente, cuando React ya ha escrito el valor restaurado en el DOM.
10778
+ //
10779
+ // Scroll: v2 ocultaba la barra mientras no se alcanzaba el tope (`data-hide-scroll` →
10780
+ // `scrollbar-hide`, `@heroui/theme@2.4.15`); aquí se consigue lo mismo con `overflow-y: hidden`
10781
+ // inline hasta que el contenido supera el máximo, y a partir de ahí se deja el scroll nativo.
10782
+ const HIDDEN_TEXTAREA_STYLE = {
10783
+ "min-height": "0",
10784
+ "max-height": "none",
10785
+ height: "0",
10786
+ visibility: "hidden",
10787
+ overflow: "hidden",
10788
+ position: "absolute",
10789
+ "z-index": "-1000",
10790
+ top: "0",
10791
+ right: "0",
10792
+ display: "block",
10793
+ };
10794
+ // Lista literal de `react-textarea-autosize` (`SIZING_STYLE`).
10795
+ const SIZING_STYLE = [
10796
+ "borderBottomWidth",
10797
+ "borderLeftWidth",
10798
+ "borderRightWidth",
10799
+ "borderTopWidth",
10800
+ "boxSizing",
10801
+ "fontFamily",
10802
+ "fontSize",
10803
+ "fontStyle",
10804
+ "fontWeight",
10805
+ "letterSpacing",
10806
+ "lineHeight",
10807
+ "paddingBottom",
10808
+ "paddingLeft",
10809
+ "paddingRight",
10810
+ "paddingTop",
10811
+ "tabSize",
10812
+ "textIndent",
10813
+ "textRendering",
10814
+ "textTransform",
10815
+ "width",
10816
+ "wordBreak",
10817
+ "wordSpacing",
10818
+ "scrollbarGutter",
10819
+ ];
10820
+ let hiddenTextarea = null;
10821
+ const forceHiddenStyles = (node) => {
10822
+ for (const key of Object.keys(HIDDEN_TEXTAREA_STYLE)) {
10823
+ node.style.setProperty(key, HIDDEN_TEXTAREA_STYLE[key], "important");
10824
+ }
10825
+ };
10826
+ const px = (value) => Number.parseFloat(value) || 0;
10827
+ /**
10828
+ * Mide el alto que necesita `node` para su contenido y lo acota entre `minRows` y `maxRows`.
10829
+ * Devuelve `null` si el nodo está fuera del DOM (no hay estilo computado que leer).
10830
+ */
10831
+ function measureTextareaHeight(node, minRows, maxRows) {
10832
+ // v2 detectaba el nodo desconectado por un `boxSizing` vacío; aquí se pregunta directamente,
10833
+ // y un `boxSizing` vacío en un nodo conectado se trata como `content-box` (el valor inicial).
10834
+ if (!node.isConnected) {
10835
+ return null;
10836
+ }
10837
+ const computed = window.getComputedStyle(node);
10838
+ const isBorderBox = computed.boxSizing === "border-box";
10839
+ const paddingSize = px(computed.paddingTop) + px(computed.paddingBottom);
10840
+ const borderSize = px(computed.borderTopWidth) + px(computed.borderBottomWidth);
10841
+ if (!hiddenTextarea) {
10842
+ hiddenTextarea = document.createElement("textarea");
10843
+ hiddenTextarea.setAttribute("tabindex", "-1");
10844
+ hiddenTextarea.setAttribute("aria-hidden", "true");
10845
+ }
10846
+ if (hiddenTextarea.parentNode === null) {
10847
+ document.body.appendChild(hiddenTextarea);
10848
+ }
10849
+ const clone = hiddenTextarea;
10850
+ const cloneStyle = clone.style;
10851
+ for (const key of SIZING_STYLE) {
10852
+ cloneStyle[key] =
10853
+ computed[key] ?? "";
10854
+ }
10855
+ forceHiddenStyles(clone);
10856
+ const contentOf = () => isBorderBox
10857
+ ? clone.scrollHeight + borderSize
10858
+ : clone.scrollHeight - paddingSize;
10859
+ const text = node.value || node.placeholder || "x";
10860
+ clone.value = text;
10861
+ let content = contentOf();
10862
+ // Doble asignación y medida: bug de Firefox que v2 también esquiva (bugzilla 1795904).
10863
+ clone.value = text;
10864
+ content = contentOf();
10865
+ clone.value = "x";
10866
+ const rowHeight = clone.scrollHeight - paddingSize;
10867
+ const extra = isBorderBox ? paddingSize + borderSize : 0;
10868
+ const minHeight = rowHeight * minRows + extra;
10869
+ const maxHeight = rowHeight * maxRows + extra;
10870
+ return {
10871
+ height: Math.min(maxHeight, Math.max(minHeight, content)),
10872
+ rowHeight,
10873
+ overflows: content > maxHeight,
10874
+ };
10875
+ }
10876
+ // `useLayoutEffect` en el servidor avisa en React 18; el alto solo tiene sentido en el cliente.
10877
+ const useIsomorphicLayoutEffect = typeof window !== "undefined" ? $8WNJc$react.useLayoutEffect : $8WNJc$react.useEffect;
10878
+ /**
10879
+ * Autocrecimiento del `<textarea>` apuntado por `nodeRef`. Devuelve el `resize` para llamarlo
10880
+ * desde el `onChange` de la fachada.
10881
+ */
10882
+ function useTextareaAutosize(nodeRef, { enabled, minRows, maxRows }) {
10883
+ const heightRef = $8WNJc$react.useRef(0);
10884
+ const overflowRef = $8WNJc$react.useRef(null);
10885
+ const resize = $8WNJc$react.useCallback(() => {
10886
+ const node = nodeRef.current;
10887
+ if (!(enabled && node)) {
10888
+ return;
10889
+ }
10890
+ const result = measureTextareaHeight(node, minRows, maxRows);
10891
+ if (!result) {
10892
+ return;
10893
+ }
10894
+ if (heightRef.current !== result.height) {
10895
+ heightRef.current = result.height;
10896
+ node.style.setProperty("height", `${result.height}px`, "important");
10897
+ }
10898
+ if (overflowRef.current !== result.overflows) {
10899
+ overflowRef.current = result.overflows;
10900
+ node.style.setProperty("overflow-y", result.overflows ? "auto" : "hidden");
10901
+ }
10902
+ }, [enabled, minRows, maxRows, nodeRef]);
10903
+ // La función más reciente para los listeners, que se registran una sola vez.
10904
+ const resizeRef = $8WNJc$react.useRef(resize);
10905
+ resizeRef.current = resize;
10906
+ // Cada render, como v2: el valor controlado, el `size` o la clase pueden haber cambiado.
10907
+ useIsomorphicLayoutEffect(() => {
10908
+ resize();
10909
+ });
10910
+ // Al apagar el autosize, se retira lo que se escribió a mano para que mande el CSS.
10911
+ useIsomorphicLayoutEffect(() => {
10912
+ const node = nodeRef.current;
10913
+ if (enabled || !node) {
10914
+ return;
10915
+ }
10916
+ node.style.removeProperty("height");
10917
+ node.style.removeProperty("overflow-y");
10918
+ heightRef.current = 0;
10919
+ overflowRef.current = null;
10920
+ }, [enabled, nodeRef]);
10921
+ $8WNJc$react.useEffect(() => {
10922
+ const node = nodeRef.current;
10923
+ if (!(enabled && node)) {
10924
+ return;
10925
+ }
10926
+ const cleanups = [];
10927
+ // Ancho: re-ajuste de línea. Solo cuando cambia el ANCHO, no el alto que ponemos nosotros.
10928
+ if (typeof ResizeObserver !== "undefined") {
10929
+ let lastWidth = node.getBoundingClientRect().width;
10930
+ const observer = new ResizeObserver((entries) => {
10931
+ const width = entries[0]?.contentRect.width ?? lastWidth;
10932
+ if (width !== lastWidth) {
10933
+ lastWidth = width;
10934
+ resizeRef.current();
10935
+ }
10936
+ });
10937
+ observer.observe(node);
10938
+ cleanups.push(() => observer.disconnect());
10939
+ }
10940
+ else {
10941
+ const onWindowResize = () => resizeRef.current();
10942
+ window.addEventListener("resize", onWindowResize);
10943
+ cleanups.push(() => window.removeEventListener("resize", onWindowResize));
10944
+ }
10945
+ const fonts = document.fonts;
10946
+ if (fonts && typeof fonts.addEventListener === "function") {
10947
+ const onFonts = () => resizeRef.current();
10948
+ fonts.addEventListener("loadingdone", onFonts);
10949
+ cleanups.push(() => fonts.removeEventListener("loadingdone", onFonts));
10950
+ }
10951
+ const onReset = (event) => {
10952
+ if (node.form && node.form === event.target) {
10953
+ requestAnimationFrame(() => resizeRef.current());
10954
+ }
10955
+ };
10956
+ document.body.addEventListener("reset", onReset);
10957
+ cleanups.push(() => document.body.removeEventListener("reset", onReset));
10958
+ return () => {
10959
+ for (const cleanup of cleanups) {
10960
+ cleanup();
10961
+ }
10962
+ };
10963
+ }, [enabled, nodeRef]);
10964
+ return resize;
10965
+ }
10966
+
9376
10967
  // TECH-2459 · F3 · G1-forms — v3 divide el `Textarea` monolítico de v2 en `TextField` +
9377
10968
  // `TextArea` (el `<textarea>` real, sin `InputGroup` intermedio: a diferencia de Input, aquí el
9378
10969
  // borde/fondo/tamaño de texto viven todos en el MISMO elemento — ver `textarea.css`). Por eso no
@@ -9411,6 +11002,8 @@ const FIELD_BORDER_Y = 4;
9411
11002
  // El default de v2, literal: `({ minRows = 3, maxRows = 8, … })` en `@heroui/input@2.4.22`,
9412
11003
  // `dist/chunk-RIE4DNUQ.mjs`.
9413
11004
  const DEFAULT_MIN_ROWS = 3;
11005
+ // Mismo literal de v2: `maxRows = 8`. Tope del autocrecimiento cuando el consumidor no lo pasa.
11006
+ const DEFAULT_MAX_ROWS = 8;
9414
11007
  // ── `minRows` gana al atributo nativo `rows` ─────────────────────────────────────────────────
9415
11008
  //
9416
11009
  // Las dos props dicen lo mismo —cuántas líneas de alto tiene la caja al aparecer— así que la
@@ -9448,7 +11041,7 @@ Math.max(1, Math.round(minRows ?? rows ?? DEFAULT_MIN_ROWS));
9448
11041
  // esquina. Es el único resto que quedaba entre `textarea--default` y su referencia: 12 píxeles en
9449
11042
  // x[780..786] y[407..413], gris `102,102,102` donde v2 tiene el fondo.
9450
11043
  const RESIZE_CLASS = "resize-none";
9451
- const RADIUS_CLASS$3 = {
11044
+ const RADIUS_CLASS$2 = {
9452
11045
  none: "rounded-none",
9453
11046
  sm: "rounded-[8px]",
9454
11047
  md: "rounded-[12px]",
@@ -9566,7 +11159,12 @@ const INVALID_TEXT_CLASS = "text-danger";
9566
11159
  const Textarea = $8WNJc$react.forwardRef(({ label, description, errorMessage, id, size = "md", variant = "bordered", radius = "md",
9567
11160
  // Sin default en el destructuring a propósito: hace falta distinguir "no me la han pasado"
9568
11161
  // de un valor, para que `rows` solo se mire cuando `minRows` no viene (ver `resolveRows`).
9569
- minRows, rows, placeholder, isInvalid = false, isRequired = false, isDisabled = false, isReadOnly = false,
11162
+ minRows, rows,
11163
+ // Tope del autocrecimiento, default de v2 (8). Se desestructura también para que no
11164
+ // llegue al DOM (antes viajaba en `...rest` y React avisaba de un atributo desconocido).
11165
+ maxRows = DEFAULT_MAX_ROWS,
11166
+ // 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,
9570
11168
  // `fullWidth` por defecto a `true`: HeroUI v2 lo traía en sus `defaultVariants`
9571
11169
  // (verificado en el paquete v2 compilado, `dist/components/input.js`:
9572
11170
  // `defaultVariants: { variant:"flat", color:"default", size:"md", fullWidth:true, … }`, con
@@ -9577,7 +11175,32 @@ minRows, rows, placeholder, isInvalid = false, isRequired = false, isDisabled =
9577
11175
  fullWidth = true, value, defaultValue, onChange, onValueChange, className, classNames, name, style, ...rest }, ref) => {
9578
11176
  const generatedId = $8WNJc$react.useId();
9579
11177
  const textareaId = id || generatedId;
9580
- const fieldRows = resolveRows(minRows, rows);
11178
+ const requestedRows = resolveRows(minRows, rows);
11179
+ const maxFieldRows = Math.max(1, Math.round(maxRows));
11180
+ // Con autocrecimiento, `maxRows` es el techo también del mínimo: v2 acotaba
11181
+ // `min(máx, max(mín, contenido))`, así que con `maxRows < minRows` ganaba `maxRows`. Sin
11182
+ // autocrecimiento `maxRows` no pinta nada (v2 ni se lo pasaba al `<textarea>` plano).
11183
+ const fieldRows = disableAutosize
11184
+ ? requestedRows
11185
+ : Math.min(requestedRows, maxFieldRows);
11186
+ const innerRef = $8WNJc$react.useRef(null);
11187
+ const setRefs = $8WNJc$react.useCallback((node) => {
11188
+ innerRef.current = node;
11189
+ if (typeof ref === "function") {
11190
+ ref(node);
11191
+ }
11192
+ else if (ref) {
11193
+ ref.current = node;
11194
+ }
11195
+ }, [ref]);
11196
+ const resize = useTextareaAutosize(innerRef, {
11197
+ enabled: !disableAutosize,
11198
+ minRows: fieldRows,
11199
+ maxRows: maxFieldRows,
11200
+ });
11201
+ // El `min-height` se emite aunque haya autocrecimiento: es el alto correcto ANTES de la
11202
+ // primera medida (SSR, o sin hoja de estilos) y el suelo si la medida sale menor (p. ej.
11203
+ // `flat`, sin los 4px de borde). El `height` lo escribe después `useTextareaAutosize`.
9581
11204
  // Inline y no una clase: el valor es un número arbitrario y Tailwind 4 solo emite las clases
9582
11205
  // que encuentra escritas en el fuente, así que `min-h-[${n}px]` no existiría en el CSS. El
9583
11206
  // `style` del consumidor va DESPUÉS para que pueda pisarlo (y una clase con `!` también gana,
@@ -9586,14 +11209,32 @@ fullWidth = true, value, defaultValue, onChange, onValueChange, className, class
9586
11209
  minHeight: `${fieldRows * SIZE_LINE_HEIGHT[size] + FIELD_PADDING_Y + FIELD_BORDER_Y}px`,
9587
11210
  ...style,
9588
11211
  };
11212
+ // El estado del valor lo actualiza el `TextField` con su propio `onChange` (llega al
11213
+ // `<textarea>` por contexto y `mergeProps` encadena los dos). Aquí: medir —el nodo ya trae
11214
+ // el valor nuevo, y en modo no controlado la fachada no se re-renderiza— y avisar.
9589
11215
  const handleChange = (event) => {
11216
+ resize();
9590
11217
  onChange?.(event);
9591
11218
  onValueChange?.(event.target.value);
9592
11219
  };
9593
- 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" : "",
11220
+ return (
11221
+ // ── `value` / `defaultValue` van al `TextField`, NO al `<textarea>` ─────────────────────
11222
+ //
11223
+ // En react-aria el dueño del valor es el `TextField`: `useTextField` guarda el estado con
11224
+ // `useControlledState(value, defaultValue ?? "")` y se lo inyecta al `<textarea>` como
11225
+ // `value` por contexto (`react-aria-components/dist/private/TextField.mjs`). Antes la
11226
+ // fachada se los pasaba al `<textarea>`, así que:
11227
+ // · en modo NO controlado el `TextField` arrancaba con "" y el `<textarea>` recibía a la
11228
+ // vez `value=""` (del contexto) y `defaultValue` → React avisaba de «both value and
11229
+ // defaultValue» y el primer re-render del padre dejaba el campo VACÍO;
11230
+ // · en modo controlado funcionaba de rebote (`mergeProps` deja ganar al `value` propio),
11231
+ // pero el estado interno del `TextField` seguía en "".
11232
+ // Ahora el `TextField` tiene el valor en los dos modos, y el `reset` del formulario también
11233
+ // 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" : "",
9594
11235
  // El `className` del consumidor, en la raíz y en último lugar: el slot `base` de
9595
11236
  // v2 y la posición que le deja ganar en tailwind-merge (ver bloque de arriba).
9596
- 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, rows: fieldRows, style: fieldStyle, placeholder: placeholder, value: value, defaultValue: defaultValue, onChange: handleChange, className: react.cn(SIZE_TEXT_CLASS[size], RESIZE_CLASS, RADIUS_CLASS$3[radius], VARIANT_CLASS[variant], isInvalid ? INVALID_TEXT_CLASS : "", FIELD_FOCUS_CLASS, FIELD_SHADOW_CLASS,
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,
9597
11238
  // Incondicional, como el slot `input` de v2: el campo llena siempre su raíz,
9598
11239
  // y es la raíz la que responde a `fullWidth` y al ancho del consumidor.
9599
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 }))] }));
@@ -13855,10 +15496,12 @@ const createCurrencyFormatter = (locale, currency, fractionDigits = 0) => {
13855
15496
  return (val) => formatter.format(val);
13856
15497
  };
13857
15498
 
15499
+ const ModalFacadeContext = $8WNJc$react.createContext({});
15500
+
13858
15501
  // v2 no tiene prop `radius` en el diálogo del modal: v3 tampoco (eliminada, "usar Tailwind"), así
13859
15502
  // que se reimplementa vía className arbitrario — mismo criterio y mismos valores exactos que
13860
15503
  // Button/Card de F3.0 (RADIUS_CLASS).
13861
- const RADIUS_CLASS$2 = {
15504
+ const RADIUS_CLASS$1 = {
13862
15505
  none: "rounded-none",
13863
15506
  sm: "rounded-[8px]",
13864
15507
  md: "rounded-[12px]",
@@ -13916,39 +15559,170 @@ const MAX_WIDTH_CLASS = {
13916
15559
  * `ModalContent` tenga que ser hijo directo de `Modal.Dialog`.
13917
15560
  */
13918
15561
  const ModalCloseContext = $8WNJc$react.createContext(() => { });
15562
+ /**
15563
+ * Atributos que el `Dialog` de react-aria SÍ deja pasar a su `<section>`: `id`, `role` (vía
15564
+ * `useDialog`), `data-*` y los cuatro "labelable" (`filterDOMProps(props, { labelable: true })`
15565
+ * en `react-aria/dist/private/utils/filterDOMProps.mjs`). Estos van como props para que
15566
+ * react-aria los vea — sobre todo `aria-label`/`aria-labelledby`, que deciden si avisa de un
15567
+ * diálogo sin nombre.
15568
+ */
15569
+ const LABELABLE_PROPS = new Set([
15570
+ "aria-label",
15571
+ "aria-labelledby",
15572
+ "aria-describedby",
15573
+ "aria-details",
15574
+ ]);
15575
+ /**
15576
+ * Reparte el resto de props de `Modal` entre las dos vías de llegada al diálogo. En v2 TODAS
15577
+ * llegaban (`mergeProps(modalProps, otherProps, …)` sobre el `<section>`, sin filtrar); en v3 el
15578
+ * `Dialog` filtra, así que un `aria-busy` se perdería en silencio si solo fuera como prop. Lo que
15579
+ * no es `aria-*`/`data-*`/`id`/`role` se descarta: `ModalProps` no lo declara y v2 lo habría
15580
+ * volcado al DOM con el aviso de React de atributo desconocido.
15581
+ */
15582
+ const splitDialogDOMProps = (rest) => {
15583
+ const dialogProps = {};
15584
+ const extraAria = {};
15585
+ for (const [key, value] of Object.entries(rest)) {
15586
+ // Un `aria-labelledby={undefined}` explícito no debe borrar el nombre por defecto.
15587
+ if (value === undefined) {
15588
+ continue;
15589
+ }
15590
+ if (key === "id" ||
15591
+ key === "role" ||
15592
+ key.startsWith("data-") ||
15593
+ LABELABLE_PROPS.has(key)) {
15594
+ dialogProps[key] = value;
15595
+ }
15596
+ else if (key.startsWith("aria-")) {
15597
+ extraAria[key] = value;
15598
+ }
15599
+ }
15600
+ return {
15601
+ dialogProps: dialogProps,
15602
+ extraAria: extraAria,
15603
+ };
15604
+ };
13919
15605
  /**
13920
15606
  * Modal - fachada BeweOS sobre el `Modal` compound de HeroUI v3.
13921
15607
  *
13922
15608
  * TECH-2459 · G2-overlays-nav — v3 convierte `Modal` en compound (`Modal.Backdrop/Container/
13923
15609
  * Dialog/Header/Body/Footer/CloseTrigger`, ver `upload-file/UploadFile.tsx` D-G1-7 y la guía
13924
15610
  * oficial `modal`). Esta fachada arma esa anatomía por dentro pero conserva la API PLANA de v2
13925
- * (`isOpen`/`onOpenChange`/`size`/`radius`/`backdrop`/`scrollBehavior`/`placement`/
13926
- * `isDismissable`/`hideCloseButton` + `<ModalContent>{(onClose) => ...}</ModalContent>`), para que
13927
- * el código ya escrito en las 4 apps consumidoras siga funcionando sin cambios.
15611
+ * (`isOpen`/`onOpenChange`/`onClose`/`size`/`radius`/`backdrop`/`scrollBehavior`/`placement`/
15612
+ * `isDismissable`/`hideCloseButton` + `<ModalContent>{(onClose) => ...}</ModalContent>`), más
15613
+ * `classNames` por slot y `aria-*`/`data-*`/`id`/`role` al diálogo, para que el código ya escrito
15614
+ * en las 4 apps consumidoras siga funcionando sin cambios.
13928
15615
  *
13929
15616
  * Accesibilidad (criterio de aceptación de la issue, verificado con Playwright): el foco entra al
13930
15617
  * abrir, queda atrapado dentro del diálogo, Escape cierra (salvo `isKeyboardDismissDisabled`), el
13931
15618
  * foco vuelve al disparador al cerrar y la página no hace scroll por detrás — los cuatro los
13932
15619
  * gestiona react-aria-components internamente (`ModalOverlay`/`Dialog`), sin código propio.
13933
15620
  */
13934
- const Modal = ({ isOpen, defaultOpen, onOpenChange, size = "md", radius = "lg", backdrop = "opaque", scrollBehavior = "normal", placement, isDismissable = true, isKeyboardDismissDisabled = false, hideCloseButton = false, className, children, }) => {
13935
- return (jsxRuntime.jsx(react.Modal, { children: jsxRuntime.jsx(react.Modal.Backdrop, { isOpen: isOpen, defaultOpen: defaultOpen, onOpenChange: onOpenChange, variant: backdrop, isDismissable: isDismissable, isKeyboardDismissDisabled: isKeyboardDismissDisabled, children: jsxRuntime.jsx(react.Modal.Container, { size: SIZE_MAP[size], placement: placement, scroll: scrollBehavior === "inside" ? "inside" : undefined, children: jsxRuntime.jsx(react.Modal.Dialog, { className: react.cn(RADIUS_CLASS$2[radius], size !== "full" && MAX_WIDTH_CLASS[size], "px-6 py-9", className), children: ({ close }) => (jsxRuntime.jsxs(ModalCloseContext.Provider, { value: close, children: [!hideCloseButton && jsxRuntime.jsx(react.Modal.CloseTrigger, {}), children] })) }) }) }) }));
15621
+ const Modal = ({ isOpen, defaultOpen, onOpenChange, onClose, size = "md", radius = "lg", backdrop = "opaque", scrollBehavior = "normal", placement, isDismissable = true, isKeyboardDismissDisabled = false, hideCloseButton = false, className, classNames, children, ...rest }) => {
15622
+ // v2 aceptaba `onClose` además de `onOpenChange` y lo llamaba en cada cierre; v3 solo conoce
15623
+ // `onOpenChange`. Sin este puente, un modal controlado con `isOpen` + `onClose` (el patrón
15624
+ // v2 dominante en las apps: ~118 usos en beweossmbs frente a 8 de `onOpenChange`) no se
15625
+ // cerraba con la X, el backdrop ni Escape, porque nadie escuchaba `onOpenChange(false)`.
15626
+ //
15627
+ // Basta con engancharse aquí: TODAS las vías de cierre acaban en este `onOpenChange` del
15628
+ // `ModalOverlay`. La X (`Modal.CloseTrigger`, `slot="close"`) y el `close` del render-prop de
15629
+ // `Modal.Dialog` —el que `ModalCloseContext` pasa a `ModalContent`— llaman a `state.close()`
15630
+ // del estado del overlay, y Escape/backdrop también; ese estado (`useOverlayTriggerState` →
15631
+ // `useControlledState`) solo notifica cuando el valor cambia, así que `onClose` se dispara
15632
+ // una vez por cierre y ninguna al abrir.
15633
+ const handleOpenChange = (open) => {
15634
+ onOpenChange?.(open);
15635
+ if (!open) {
15636
+ onClose?.();
15637
+ }
15638
+ };
15639
+ // v2 nombraba el diálogo con su cabecera (`aria-labelledby` = id del `ModalHeader`,
15640
+ // `getDialogProps` de `@heroui/modal@2.2.19`); en v3 solo lo hace `Modal.Heading`, que las apps
15641
+ // no usan. Mismo arreglo que `Drawer`: `ModalHeader` pone este id en su nodo.
15642
+ //
15643
+ // Si el consumidor trae su propio `aria-label`/`aria-labelledby` (3 `<Modal>` de beweossmbs,
15644
+ // p. ej. `create-note-modal`), manda el suyo. Aquí v3 mejora a v2: v2 escribía
15645
+ // `aria-labelledby` DESPUÉS del spread, así que pisaba siempre el del consumidor.
15646
+ const headerId = $8WNJc$react.useId();
15647
+ // Props de `ModalContent` (registradas desde dentro del diálogo, ver `ModalContent.tsx`). En
15648
+ // v2 `ModalContent` era el diálogo: `getDialogProps(mergeProps(dialogProps, otherProps))`
15649
+ // (`@heroui/modal@2.2.19`, `chunk-UKDXCIYN.mjs`), y dentro de `getDialogProps`
15650
+ // (`chunk-Z2LRST4N.mjs`):
15651
+ // - atributos: `mergeProps(modalProps, <resto de Modal>, <props de ModalContent>)` →
15652
+ // ModalContent gana a Modal;
15653
+ // - clase: `slots.base({ class: clsx(clsx(classNames.base, className), content.className) })`
15654
+ // → fachada (tv) < `classNames.base` < `className` de Modal < `className` de ModalContent,
15655
+ // con `twMerge`. Es el orden del `cn` de abajo.
15656
+ const [content, setContent] = $8WNJc$react.useState({});
15657
+ const registerContentProps = $8WNJc$react.useCallback((props) => setContent(props ?? {}), []);
15658
+ const { className: contentClassName, ...contentRest } = content;
15659
+ const { dialogProps, extraAria } = splitDialogDOMProps({
15660
+ ...rest,
15661
+ ...contentRest,
15662
+ });
15663
+ const hasOwnName = dialogProps["aria-label"] !== undefined ||
15664
+ dialogProps["aria-labelledby"] !== undefined;
15665
+ const hasExtraAria = Object.keys(extraAria).length > 0;
15666
+ // Sin `Modal.Root` (el `DialogTrigger` de react-aria) a propósito, igual que `Drawer`: la
15667
+ // fachada no tiene disparador propio —la abre el `isOpen` de la app— y un `DialogTrigger`
15668
+ // sin hijo pulsable avisaba en consola en CADA montaje («A PressResponder was rendered
15669
+ // without a pressable child», medido en jsdom: 9 avisos en `Modal.onClose.test.tsx`).
15670
+ // `Modal.Backdrop` (`ModalOverlay`) ya crea y publica su propio estado de overlay con
15671
+ // `isOpen`/`onOpenChange` —el que usan la X y el `close` del render-prop— y calcula todos los
15672
+ // slots de `modalVariants()` aunque no haya root encima; el root solo aportaba ese aviso y un
15673
+ // `aria-labelledby` de reserva que apuntaba al id de un disparador que no existe.
15674
+ return (jsxRuntime.jsx(react.Modal.Backdrop, { isOpen: isOpen, defaultOpen: defaultOpen, onOpenChange: handleOpenChange, variant: backdrop, isDismissable: isDismissable, isKeyboardDismissDisabled: isKeyboardDismissDisabled,
15675
+ // v2 `classNames.backdrop` → la capa que oscurece la página. Utilidad en
15676
+ // `@layer utilities`, gana a `.modal__backdrop--opaque` (`@layer components`).
15677
+ className: classNames?.backdrop, children: jsxRuntime.jsx(react.Modal.Container, { size: SIZE_MAP[size], placement: placement, scroll: scrollBehavior === "inside" ? "inside" : undefined,
15678
+ // v2 `classNames.wrapper` (el `div` que posiciona el diálogo, `data-slot="wrapper"`)
15679
+ // es el `Modal.Container` de v3: su `p-4 sm:p-10` es lo que pisa un `wrapper="p-0"`.
15680
+ className: classNames?.wrapper, children: jsxRuntime.jsx(react.Modal.Dialog, { ...(hasOwnName ? {} : { "aria-labelledby": headerId }), ...dialogProps,
15681
+ // Los `aria-*` que react-aria filtra (`aria-busy`…) entran por el `render` del
15682
+ // `<section>`: van ANTES de sus props, así que `role`/`aria-modal`/`tabIndex`
15683
+ // de react-aria no se pueden pisar por esta vía.
15684
+ render: hasExtraAria
15685
+ ? (sectionProps) => jsxRuntime.jsx("section", { ...extraAria, ...sectionProps })
15686
+ : undefined, className: react.cn(RADIUS_CLASS$1[radius], size !== "full" && MAX_WIDTH_CLASS[size], "px-6 py-9", classNames?.base, className, contentClassName), children: ({ close }) => (jsxRuntime.jsx(ModalDialogBody, { close: close, headerId: headerId, classNames: classNames, registerContentProps: registerContentProps, hideCloseButton: hideCloseButton, children: children })) }) }) }));
15687
+ };
15688
+ /** Aísla el `useMemo` del valor de contexto (el render-prop de `Dialog` no puede usar hooks). */
15689
+ const ModalDialogBody = ({ close, headerId, classNames, registerContentProps, hideCloseButton, children, }) => {
15690
+ const value = $8WNJc$react.useMemo(() => ({ headerId, classNames, registerContentProps }), [headerId, classNames, registerContentProps]);
15691
+ return (jsxRuntime.jsx(ModalCloseContext.Provider, { value: close, children: jsxRuntime.jsxs(ModalFacadeContext.Provider, { value: value, children: [!hideCloseButton && (jsxRuntime.jsx(react.Modal.CloseTrigger, { className: classNames?.closeButton })), children] }) }));
13936
15692
  };
13937
15693
 
15694
+ /**
15695
+ * Clave estable de las props registradas: solo primitivos (`className`, `aria-*`, `data-*`…), así
15696
+ * que basta con serializarlas. Evita re-registrar —y re-renderizar el `Modal`— en cada render.
15697
+ */
15698
+ const keyOf = (props) => JSON.stringify(Object.entries(props)
15699
+ .filter(([, value]) => value !== undefined)
15700
+ .sort(([a], [b]) => (a < b ? -1 : 1)));
13938
15701
  /**
13939
15702
  * `ModalContent` de v2 no sobrevive en v3 (compound: `Modal.Container`/`Modal.Dialog`, ver D-G1-7
13940
- * / D-F2-3). Este componente NO es un wrapper de ningún compound v3 — es puro azúcar de
13941
- * compatibilidad: envuelve sus `children` (que ya se renderizan dentro de `Modal.Dialog`, ver
13942
- * `Modal.tsx`) y, si `children` es una función, la invoca con el `close` real del diálogo (vía
13943
- * contexto) para que el patrón v2 `{(onClose) => <>...</>}` siga funcionando tal cual.
15703
+ * / D-F2-3). Este componente NO es un wrapper de ningún compound v3 — es azúcar de
15704
+ * compatibilidad: sus `children` ya se renderizan dentro de `Modal.Dialog` (ver `Modal.tsx`) y, si
15705
+ * `children` es una función, la invoca con el `close` real del diálogo (vía contexto) para que el
15706
+ * patrón v2 `{(onClose) => <>...</>}` siga funcionando tal cual.
15707
+ *
15708
+ * Sus demás props (`className`, `aria-busy`, `data-*`…) NO se pintan aquí: en v2 `ModalContent`
15709
+ * era el propio diálogo, así que se registran en el `Modal` y este las aplica al `role="dialog"`
15710
+ * (antes caían en un `<div>` intermedio y, p. ej., el `h-[95vh] p-0 rounded-2xl` de
15711
+ * `chatbot-config` no llegaba al diálogo). Sin props no se registra nada: el render es el mismo.
13944
15712
  */
13945
- function ModalContent({ children, className }) {
15713
+ function ModalContent({ children, ...dialogProps }) {
13946
15714
  const close = $8WNJc$react.useContext(ModalCloseContext);
13947
- const content = typeof children === "function" ? children(close) : children;
13948
- if (!className) {
13949
- return jsxRuntime.jsx(jsxRuntime.Fragment, { children: content });
13950
- }
13951
- return jsxRuntime.jsx("div", { className: className, children: content });
15715
+ const { registerContentProps } = $8WNJc$react.useContext(ModalFacadeContext);
15716
+ const key = keyOf(dialogProps);
15717
+ // biome-ignore lint/correctness/useExhaustiveDependencies: `key` resume `dialogProps` (primitivos); depender del objeto re-registraría en cada render.
15718
+ $8WNJc$react.useLayoutEffect(() => {
15719
+ if (!registerContentProps || key === "[]") {
15720
+ return;
15721
+ }
15722
+ registerContentProps(dialogProps);
15723
+ return () => registerContentProps(undefined);
15724
+ }, [registerContentProps, key]);
15725
+ return jsxRuntime.jsx(jsxRuntime.Fragment, { children: typeof children === "function" ? children(close) : children });
13952
15726
  }
13953
15727
 
13954
15728
  /**
@@ -13960,9 +15734,23 @@ function ModalContent({ children, className }) {
13960
15734
  * era una fila sin gap.
13961
15735
  *
13962
15736
  * El `className` del consumidor se mezcla con `cn` (tailwind-merge), así que sigue
13963
- * ganando: `<ModalHeader className="flex flex-col gap-1">` mantiene su columna.
15737
+ * ganando: `<ModalHeader className="flex flex-col gap-1">` mantiene su columna. El slot
15738
+ * `classNames.header` del `<Modal>` se aplica entre medias (gana a la fachada, pierde con el
15739
+ * `className`), y el nodo lleva el `id` que nombra el diálogo, como en v2.
13964
15740
  */
13965
- const ModalHeader = ({ className, ...props }) => (jsxRuntime.jsx(react.ModalHeader, { ...props, className: react.cn("flex-row gap-0 px-6 py-4 text-lg font-semibold", className) }));
15741
+ const ModalHeader = ({ className, ...props }) => {
15742
+ const { headerId, classNames } = $8WNJc$react.useContext(ModalFacadeContext);
15743
+ return (jsxRuntime.jsx(react.ModalHeader
15744
+ // Nombra el diálogo (`aria-labelledby`, ver `Modal.tsx`). Va antes del spread: un `id`
15745
+ // propio del consumidor gana, igual que en v2 (`id: headerId, ...otherProps`).
15746
+ , {
15747
+ // Nombra el diálogo (`aria-labelledby`, ver `Modal.tsx`). Va antes del spread: un `id`
15748
+ // propio del consumidor gana, igual que en v2 (`id: headerId, ...otherProps`).
15749
+ id: headerId, ...props, className: react.cn("flex-row gap-0 px-6 py-4 text-lg font-semibold",
15750
+ // v2 `classNames.header` del `<Modal>`; el `className` propio gana
15751
+ // (`clsx(classNames.header, className)` en `@heroui/modal@2.2.19`).
15752
+ classNames?.header, className) }));
15753
+ };
13966
15754
  ModalHeader.displayName = "ModalHeader";
13967
15755
 
13968
15756
  /**
@@ -13978,8 +15766,9 @@ ModalHeader.displayName = "ModalHeader";
13978
15766
  * 24px de `px-6` ese margen negativo solo desalineaba el contenido 3px respecto al header.
13979
15767
  * `mt-0` anula el `.modal__header + .modal__body { mt-2 }` de v3, que v2 no tenía.
13980
15768
  */
13981
- const BODY_V2_GEOMETRY = "mx-0 mt-0 flex flex-1 flex-col gap-3 px-6 py-2";
15769
+ const BODY_V2_GEOMETRY$1 = "mx-0 mt-0 flex flex-1 flex-col gap-3 px-6 py-2";
13982
15770
  const ModalBody = $8WNJc$react.forwardRef(({ className, ...props }, forwardedRef) => {
15771
+ const { classNames } = $8WNJc$react.useContext(ModalFacadeContext);
13983
15772
  const nodeRef = $8WNJc$react.useRef(null);
13984
15773
  const callbackRef = $8WNJc$react.useCallback((node) => {
13985
15774
  nodeRef.current = node;
@@ -14125,19 +15914,217 @@ const ModalBody = $8WNJc$react.forwardRef(({ className, ...props }, forwardedRef
14125
15914
  track.remove();
14126
15915
  };
14127
15916
  }, []);
14128
- return (jsxRuntime.jsx(react.ModalBody, { ref: callbackRef, ...props, className: react.cn(BODY_V2_GEOMETRY, className) }));
15917
+ return (jsxRuntime.jsx(react.ModalBody, { ref: callbackRef, ...props,
15918
+ // v2 `classNames.body` del `<Modal>` entre la geometría y el `className` propio,
15919
+ // que gana (`clsx(classNames.body, className)` en `@heroui/modal@2.2.19`).
15920
+ className: react.cn(BODY_V2_GEOMETRY$1, classNames?.body, className) }));
15921
+ });
15922
+ ModalBody.displayName = "ModalBody";
15923
+
15924
+ /**
15925
+ * Geometría v2 del slot `footer` del modal (TECH-2459 / F5 · P-15). v2:
15926
+ * `flex flex-row gap-2 px-6 py-4 justify-end` (`@heroui/theme@2.4.15`, slot `footer`).
15927
+ * v3 conserva `flex-row justify-end gap-2` pero pierde el padding, y añade un
15928
+ * `.modal__body + .modal__footer { mt-5 }` que v2 no tenía (v2 separaba los slots solo
15929
+ * con su propio padding) — de ahí el `mt-0`.
15930
+ */
15931
+ const ModalFooter = ({ className, ...props }) => {
15932
+ const { classNames } = $8WNJc$react.useContext(ModalFacadeContext);
15933
+ return (jsxRuntime.jsx(react.ModalFooter, { ...props,
15934
+ // v2 `classNames.footer` del `<Modal>` entre la fachada y el `className` propio, que
15935
+ // gana (`clsx(classNames.footer, className)` en `@heroui/modal@2.2.19`).
15936
+ className: react.cn("mt-0 px-6 py-4", classNames?.footer, className) }));
15937
+ };
15938
+ ModalFooter.displayName = "ModalFooter";
15939
+
15940
+ const DrawerFacadeContext = $8WNJc$react.createContext({
15941
+ close: () => {
15942
+ // fuera de un `Drawer` no hay diálogo que cerrar
15943
+ },
15944
+ scrollBehavior: "inside",
15945
+ });
15946
+
15947
+ // Tope del lado corto del panel por tamaño, con los valores EXACTOS de v2 — leídos del oráculo,
15948
+ // no de memoria: `@heroui/theme@2.4.15`, `dist/chunk-EP7KPCL3.mjs` (tv `drawer`) aplica
15949
+ // `max-w-<size>` + `max-h-[<rem>]` al slot `base` (el panel), y el `placement` anula el eje que
15950
+ // no toca (`max-h-[none]` en left/right, `max-w-[none]` en top/bottom). La escala `maxWidth` de
15951
+ // Tailwind 3 resuelve `max-w-<size>` a los mismos rem que el `max-h` literal. Clases literales:
15952
+ // Tailwind solo genera lo que ve escrito entero en el fuente.
15953
+ //
15954
+ // left/right: v2 era `w-full` (slot `base` del modal) + `max-w-<size>`. v3 fija
15955
+ // `w-80 max-w-[85vw] sm:w-96` (320/384px) para cualquier tamaño: sin esto, `size="lg"` medía
15956
+ // 384px donde v2 medía 512px, y `size` no hacía nada. Ganan a `.drawer__dialog[data-placement]`
15957
+ // por capa (`@layer components` < `@layer utilities`), no por especificidad, igual que el
15958
+ // `MAX_WIDTH_CLASS` de `Modal`.
15959
+ const HORIZONTAL_SIZE_CLASS = {
15960
+ xs: "w-full max-w-[20rem]",
15961
+ sm: "w-full max-w-[24rem]",
15962
+ md: "w-full max-w-[28rem]",
15963
+ lg: "w-full max-w-[32rem]",
15964
+ xl: "w-full max-w-[36rem]",
15965
+ "2xl": "w-full max-w-[42rem]",
15966
+ "3xl": "w-full max-w-[48rem]",
15967
+ "4xl": "w-full max-w-[56rem]",
15968
+ "5xl": "w-full max-w-[64rem]",
15969
+ };
15970
+ // top/bottom: v3 ya es `w-full` y topa el alto a `max-h-[85vh]`; v2 lo topaba por tamaño.
15971
+ const VERTICAL_SIZE_CLASS = {
15972
+ xs: "max-h-[20rem]",
15973
+ sm: "max-h-[24rem]",
15974
+ md: "max-h-[28rem]",
15975
+ lg: "max-h-[32rem]",
15976
+ xl: "max-h-[36rem]",
15977
+ "2xl": "max-h-[42rem]",
15978
+ "3xl": "max-h-[48rem]",
15979
+ "4xl": "max-h-[56rem]",
15980
+ "5xl": "max-h-[64rem]",
15981
+ };
15982
+ // v2 `full`: `max-w-full max-h-full h-[100dvh] !rounded-none` en cualquier `placement`.
15983
+ const FULL_SIZE_CLASS = "w-full max-w-full h-[100dvh] max-h-full rounded-none";
15984
+ const sizeClass = (size, placement) => {
15985
+ if (size === "full") {
15986
+ return FULL_SIZE_CLASS;
15987
+ }
15988
+ return placement === "left" || placement === "right"
15989
+ ? HORIZONTAL_SIZE_CLASS[size]
15990
+ : VERTICAL_SIZE_CLASS[size];
15991
+ };
15992
+ /**
15993
+ * Drawer - fachada BeweOS sobre el `Drawer` compound de HeroUI v3.
15994
+ *
15995
+ * TECH-2459 — hasta el 23-sep `Drawer`/`DrawerContent`/`DrawerHeader`/`DrawerBody`/`DrawerFooter`
15996
+ * se servían CRUDOS de v3, que es compound y no entiende la API plana de v2: `isOpen`/`onClose`
15997
+ * no llegaban a ningún overlay, así que los drawers de las apps no montaban o no cerraban. Esta
15998
+ * fachada arma la anatomía de v3 por dentro (`Drawer` → `Backdrop` → `Content` → `Dialog`) y
15999
+ * conserva la API de v2 (`isOpen`/`defaultOpen`/`onOpenChange`/`onClose`/`placement`/`size`/
16000
+ * `backdrop`/`scrollBehavior`/`isDismissable`/`isKeyboardDismissDisabled`/`hideCloseButton` +
16001
+ * `<DrawerContent>{(onClose) => ...}</DrawerContent>`), con los defaults de v2 (`useDrawer` de
16002
+ * `@heroui/drawer@2.2.29`: `placement="right"`, `size="md"`, `scrollBehavior="inside"`).
16003
+ *
16004
+ * Lo que NO se repone a propósito (criterio de la migración: adaptarse a v3, no identidad de
16005
+ * píxel): fondo, sombra, radio y posición de la X son los de v3. Sí se repone la geometría de la
16006
+ * que depende el contenido de las apps: el ancho/alto por `size`, el padding de los slots
16007
+ * (`DrawerHeader`/`Body`/`Footer`) y el panel sin padding propio, porque las apps ya compensan
16008
+ * sobre esos valores (p. ej. `DrawerHeader className="pt-6"`).
16009
+ *
16010
+ * La anatomía v3 completa queda publicada como estáticos (`Drawer.Backdrop`, `Drawer.Content`,
16011
+ * `Drawer.Dialog`, …): son los MISMOS objetos que exporta `@heroui/react`.
16012
+ */
16013
+ const DrawerFacade = ({ isOpen, defaultOpen, onOpenChange, onClose, placement = "right", size = "md", backdrop = "opaque", scrollBehavior = "inside", isDismissable = true, isKeyboardDismissDisabled = false, hideCloseButton = false, className, children, }) => {
16014
+ // Mismo puente que `Modal` (f3ce18c): v3 solo conoce `onOpenChange`. Todas las vías de cierre
16015
+ // (la X —`Drawer.CloseTrigger`, `slot="close"`—, el `close` del render-prop de
16016
+ // `Drawer.Dialog`, Escape, backdrop y el arrastre de v3) acaban en `state.close()` del
16017
+ // overlay, que solo notifica cuando el valor cambia: `onClose` sale una vez por cierre y
16018
+ // ninguna al abrir.
16019
+ // v2 nombraba el diálogo con su cabecera (`ModalContent` → `aria-labelledby` = id del
16020
+ // `ModalHeader`). En v3 solo lo hace `Drawer.Heading`, que las apps no usan: sin esto el
16021
+ // panel quedaba sin nombre accesible y react-aria avisaba en consola en cada apertura («A
16022
+ // dialog must have a title for accessibility», medido en jsdom). `DrawerHeader` pone este id.
16023
+ const headerId = $8WNJc$react.useId();
16024
+ const handleOpenChange = (open) => {
16025
+ onOpenChange?.(open);
16026
+ if (!open) {
16027
+ onClose?.();
16028
+ }
16029
+ };
16030
+ // Sin `Drawer.Root` (el `DialogTrigger` de react-aria) a propósito: la fachada no tiene
16031
+ // disparador propio —lo abre el `isOpen` de la app— y un `DialogTrigger` sin hijo pulsable
16032
+ // 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`)
16034
+ // ya crea y publica su propio estado de overlay con `isOpen`/`onOpenChange`, y trae todos los
16035
+ // slots de `drawerVariants()`, así que el root no aportaba nada.
16036
+ 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 })) }) }) }));
16037
+ };
16038
+ /** Aísla el `useMemo` del valor de contexto (el render-prop de `Dialog` no puede usar hooks). */
16039
+ const DrawerDialogBody = ({ close, headerId, scrollBehavior, hideCloseButton, children }) => {
16040
+ const value = $8WNJc$react.useMemo(() => ({ close, headerId, scrollBehavior }), [close, headerId, scrollBehavior]);
16041
+ return (jsxRuntime.jsxs(DrawerFacadeContext.Provider, { value: value, children: [!hideCloseButton && jsxRuntime.jsx(react.Drawer.CloseTrigger, {}), children] }));
16042
+ };
16043
+ DrawerFacade.displayName = "Aurora.Drawer";
16044
+ /**
16045
+ * `Drawer` de Aurora: fachada v2 + estáticos compound de v3 (los mismos objetos que exporta
16046
+ * `@heroui/react`), para quien quiera montar la anatomía de v3 a mano.
16047
+ *
16048
+ * ⚠️ `Drawer.Content`/`Drawer.Header`/`Drawer.Body`/`Drawer.Footer` son las piezas CRUDAS de v3
16049
+ * (sin la geometría v2 ni el render-prop): no son los `DrawerContent`/`DrawerHeader`/… planos de
16050
+ * esta librería. No se mezclan las dos formas en el mismo drawer.
16051
+ */
16052
+ const Drawer = Object.assign(DrawerFacade, {
16053
+ Root: react.Drawer.Root,
16054
+ Trigger: react.Drawer.Trigger,
16055
+ Backdrop: react.Drawer.Backdrop,
16056
+ Content: react.Drawer.Content,
16057
+ Dialog: react.Drawer.Dialog,
16058
+ Header: react.Drawer.Header,
16059
+ Heading: react.Drawer.Heading,
16060
+ Body: react.Drawer.Body,
16061
+ Footer: react.Drawer.Footer,
16062
+ Handle: react.Drawer.Handle,
16063
+ CloseTrigger: react.Drawer.CloseTrigger,
14129
16064
  });
14130
- ModalBody.displayName = "ModalBody";
14131
16065
 
14132
16066
  /**
14133
- * Geometría v2 del slot `footer` del modal (TECH-2459 / F5 · P-15). v2:
14134
- * `flex flex-row gap-2 px-6 py-4 justify-end` (`@heroui/theme@2.4.15`, slot `footer`).
14135
- * v3 conserva `flex-row justify-end gap-2` pero pierde el padding, y añade un
14136
- * `.modal__body + .modal__footer { mt-5 }` que v2 no tenía (v2 separaba los slots solo
14137
- * con su propio padding) — de ahí el `mt-0`.
16067
+ * `DrawerContent` de v2 (en v2 era el mismo `ModalContent`). En v3 `Drawer.Content` es el
16068
+ * posicionador del overlay, que la fachada `Drawer` ya monta por dentro; este componente NO lo
16069
+ * envuelve: es azúcar de compatibilidad. Si `children` es una función, la invoca con el `close`
16070
+ * real del diálogo (vía contexto) para que el patrón v2 `{(onClose) => <>...</>}` siga
16071
+ * funcionando (6 de los 12 drawers de las apps lo usan, medido el 23-sep).
14138
16072
  */
14139
- const ModalFooter = ({ className, ...props }) => (jsxRuntime.jsx(react.ModalFooter, { ...props, className: react.cn("mt-0 px-6 py-4", className) }));
14140
- ModalFooter.displayName = "ModalFooter";
16073
+ function DrawerContent({ children, className }) {
16074
+ const { close } = $8WNJc$react.useContext(DrawerFacadeContext);
16075
+ const content = typeof children === "function" ? children(close) : children;
16076
+ if (!className) {
16077
+ return jsxRuntime.jsx(jsxRuntime.Fragment, { children: content });
16078
+ }
16079
+ return jsxRuntime.jsx("div", { className: className, children: content });
16080
+ }
16081
+ DrawerContent.displayName = "Aurora.DrawerContent";
16082
+
16083
+ /**
16084
+ * Geometría v2 del slot `header`, reintroducida sobre `.drawer__header` de v3. v2 (el drawer
16085
+ * reusaba los slots del modal): `flex py-4 px-6 flex-initial text-large font-semibold`
16086
+ * (`@heroui/theme@2.4.15`, `chunk-YXVO2HGK.mjs`, slot `header`; `text-large` → `text-lg`).
16087
+ * `.drawer__header` de v3 no tiene padding ni tipografía y apila en columna con `gap-3`; mismo
16088
+ * arreglo que `ModalHeader`.
16089
+ *
16090
+ * El `className` del consumidor se mezcla con `cn` (tailwind-merge) en última posición, así que
16091
+ * sigue ganando: `className="flex flex-col gap-1"` mantiene su columna y `pt-6` pisa el `py-4`.
16092
+ *
16093
+ * Lleva el `id` que el panel usa como `aria-labelledby`: la cabecera NOMBRA el diálogo, como en
16094
+ * v2 (ver `Drawer.tsx`).
16095
+ */
16096
+ const DrawerHeader = ({ className, children }) => {
16097
+ const { headerId } = $8WNJc$react.useContext(DrawerFacadeContext);
16098
+ return (jsxRuntime.jsx(react.DrawerHeader, { id: headerId, className: react.cn("flex-initial flex-row gap-0 px-6 py-4 text-lg font-semibold", className), children: children }));
16099
+ };
16100
+ DrawerHeader.displayName = "Aurora.DrawerHeader";
16101
+
16102
+ /**
16103
+ * Geometría v2 del slot `body`: `flex flex-1 flex-col gap-3 px-6 py-2` (`@heroui/theme@2.4.15`,
16104
+ * `chunk-YXVO2HGK.mjs`, slot `body`). `.drawer__body` de v3 es un bloque sin padding ni gap
16105
+ * (`-m-[3px] my-0 p-[3px]`), así que los campos de un formulario quedarían pegados y a ras del
16106
+ * borde. `mx-0` anula el margen negativo de v3 y `mt-0` el `.drawer__header + .drawer__body
16107
+ * { mt-2 }`, que v2 no tenía (mismo arreglo que `ModalBody`).
16108
+ *
16109
+ * Scroll: con `scrollBehavior="inside"` (default v2 del drawer) el cuerpo hace scroll, que es lo
16110
+ * que ya trae `.drawer__body` de v3. Con `outside` el que hace scroll es el panel, así que el
16111
+ * cuerpo lo suelta (`overflow-visible`) y deja de encogerse (`flex-none`).
16112
+ */
16113
+ const BODY_V2_GEOMETRY = "mx-0 mt-0 flex flex-1 flex-col gap-3 px-6 py-2";
16114
+ const DrawerBody = ({ className, children }) => {
16115
+ const { scrollBehavior } = $8WNJc$react.useContext(DrawerFacadeContext);
16116
+ return (jsxRuntime.jsx(react.DrawerBody, { className: react.cn(BODY_V2_GEOMETRY, scrollBehavior === "outside" && "flex-none overflow-visible", className), children: children }));
16117
+ };
16118
+ DrawerBody.displayName = "Aurora.DrawerBody";
16119
+
16120
+ /**
16121
+ * Geometría v2 del slot `footer`: `flex flex-row gap-2 px-6 py-4 justify-end`
16122
+ * (`@heroui/theme@2.4.15`, slot `footer`). v3 conserva `flex-row justify-end gap-2` pero pierde
16123
+ * 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`).
16125
+ */
16126
+ const DrawerFooter = ({ className, children }) => (jsxRuntime.jsx(react.DrawerFooter, { className: react.cn("mt-0 px-6 py-4", className), children: children }));
16127
+ DrawerFooter.displayName = "Aurora.DrawerFooter";
14141
16128
 
14142
16129
  // TECH-2459 — Fachada `Alert` (hallazgo loginos 07-sep: `index.tsx` reexportaba el `Alert` CRUDO de
14143
16130
  // v3 y solo shimeaba el TIPO `AlertProps` v2 → el JSX v2 compilaba pero el componente era v3).
@@ -14181,7 +16168,7 @@ const COLOR_BY_STATUS = {
14181
16168
  };
14182
16169
  // v3 usa `min(32px, var(--radius-3xl))`; v2 `radius="md"` = `rounded-medium` = 12px. Mismos
14183
16170
  // valores EXACTOS que Button/Card/Chip (los `rounded-md`/`lg` de Tailwind son 6/8px, no 12/14).
14184
- const RADIUS_CLASS$1 = {
16171
+ const RADIUS_CLASS = {
14185
16172
  none: "rounded-none",
14186
16173
  sm: "rounded-[8px]",
14187
16174
  md: "rounded-[12px]",
@@ -14317,291 +16304,192 @@ function slotClasses(variant, color) {
14317
16304
  description: defaultDescription,
14318
16305
  iconWrapper: `${ICON_WRAPPER_SHADOW} ${BORDERED_ICON_WRAPPER[color]}`,
14319
16306
  closeButton: BORDERED_CLOSE[color],
14320
- };
14321
- case "faded":
14322
- return {
14323
- base: `border ${FLAT_BASE[color]} ${FADED_BORDER[color]}`,
14324
- description: defaultDescription,
14325
- iconWrapper: `${ICON_WRAPPER_TINTED} ${FLAT_ICON_WRAPPER[color]}`,
14326
- closeButton: FLAT_CLOSE[color],
14327
- };
14328
- default:
14329
- return {
14330
- base: FLAT_BASE[color],
14331
- description: defaultDescription,
14332
- iconWrapper: `${ICON_WRAPPER_TINTED} ${FLAT_ICON_WRAPPER[color]}`,
14333
- closeButton: FLAT_CLOSE[color],
14334
- };
14335
- }
14336
- }
14337
- // Los subcomponentes compound de v3, adjuntos como estáticos (`Alert.Indicator`…) para que el JSX
14338
- // v3 (loginos ya migró el suyo) siga compilando y renderizando IGUAL que contra el `Alert` crudo.
14339
- const COMPOUND_PARTS$1 = new Set([
14340
- react.Alert.Indicator,
14341
- react.Alert.Content,
14342
- react.Alert.Title,
14343
- react.Alert.Description,
14344
- ]);
14345
- /**
14346
- * Detecta la API compound de v3: si algún hijo directo es `Alert.Indicator`/`Content`/`Title`/
14347
- * `Description`, el consumidor ya compone la estructura y la fachada NO debe envolver nada (si lo
14348
- * hiciera, saldrían dos indicadores). `<Alert>Texto</Alert>` (sin partes compound) sigue siendo
14349
- * API v2: children bajo la descripción, con icono y círculo, como en v2.
14350
- */
14351
- function hasCompoundChildren$1(children) {
14352
- return $8WNJc$react.Children.toArray(children).some((child) => $8WNJc$react.isValidElement(child) && COMPOUND_PARTS$1.has(child.type));
14353
- }
14354
- /** `isEmpty` de v2 (`@heroui/shared-utils`): null/undefined/"" no cuentan como contenido. */
14355
- function hasValue$1(node) {
14356
- return node !== null && node !== undefined && node !== "" && node !== false;
14357
- }
14358
- /**
14359
- * Alert - Aviso contextual basado en HeroUI v3 con la API pública de v2 intacta (TECH-2459).
14360
- *
14361
- * Dos modos, decididos por los `children`:
14362
- * - **API v2** (`color`/`variant`/`title`/`description`/`isClosable`…): la fachada compone el
14363
- * compound de v3 y repone `variant` (fondo teñido en `flat`), `radius`, el círculo del icono,
14364
- * el botón de cerrar (`CloseButton` de v3 con `aria-label` traducido) y la visibilidad.
14365
- * - **API compound v3** (`<Alert status="danger"><Alert.Indicator/><Alert.Content>…`): pasa tal
14366
- * cual al `Alert` de HeroUI; `color`, si viene, se traduce a `status`.
14367
- */
14368
- const AlertFacade = ({ color, status, variant = "flat", radius = "md", title, description, icon, hideIcon = false, hideIconWrapper = false, isClosable = false, onClose, isVisible: isVisibleProp, isDefaultVisible, onVisibleChange, startContent, endContent, classNames, className, children, translations = {}, ...rest }) => {
14369
- // `useControlledState` de v2 (`use-alert.ts`): controlado si llega `isVisible`; si no, estado
14370
- // propio que arranca en `isDefaultVisible ?? true` y se apaga al cerrar.
14371
- const [uncontrolledVisible, setUncontrolledVisible] = $8WNJc$react.useState(isDefaultVisible ?? true);
14372
- const isControlled = isVisibleProp !== undefined;
14373
- const isVisible = isControlled ? isVisibleProp : uncontrolledVisible;
14374
- const t = { ...DEFAULT_TRANSLATIONS, ...translations };
14375
- // `status` (v3) gana sobre `color` (v2) para la clase de estado de HeroUI; y si SOLO llega
14376
- // `status`, las utilidades de Aurora siguen el color equivalente (`accent` → `primary`).
14377
- const effectiveColor = color ?? (status ? COLOR_BY_STATUS[status] : "default");
14378
- const effectiveStatus = status ?? STATUS_BY_COLOR[effectiveColor];
14379
- if (!isVisible) {
14380
- return null;
14381
- }
14382
- if (hasCompoundChildren$1(children)) {
14383
- return (jsxRuntime.jsx(react.Alert, { ...rest, status: effectiveStatus, className: react.cn(classNames?.base, className), children: children }));
14384
- }
14385
- const handleClose = () => {
14386
- if (!isControlled) {
14387
- setUncontrolledVisible(false);
14388
- }
14389
- onVisibleChange?.(false);
14390
- onClose?.();
14391
- };
14392
- const slots = slotClasses(variant, effectiveColor);
14393
- const hasTitle = hasValue$1(title);
14394
- const hasDescription = hasValue$1(description);
14395
- // `hasContent` de v2: sin descripción ni children, el título se centra verticalmente con el icono.
14396
- const hasContent = hasDescription || hasValue$1(children);
14397
- const showClose = isClosable || Boolean(onClose);
14398
- return (jsxRuntime.jsxs(react.Alert, { role: "alert", "data-visible": "true", "data-closeable": showClose ? "true" : undefined, "data-has-title": hasTitle ? "true" : undefined, "data-has-description": hasDescription ? "true" : undefined, ...rest, status: effectiveStatus,
14399
- // Base v2: `flex flex-grow flex-row w-full items-start py-3 px-4 gap-x-1`. v3 ya trae
14400
- // flex/w-full/items-start/px-4/py-3; se repone el `gap-x-1` (v3: `gap-4`), se quita el
14401
- // `shadow-surface` (v2 no tenía sombra en el base) y se aplica variant/radius.
14402
- className: react.cn("grow gap-x-1 shadow-none", slots.base, RADIUS_CLASS$1[radius], hideIconWrapper && "gap-x-0", classNames?.base, className), children: [startContent, !hideIcon && (jsxRuntime.jsx(react.Alert.Indicator, { className: react.cn(ICON_WRAPPER_BASE,
14403
- // v2 `hideIconWrapper`: `!bg-transparent !shadow-none !border-none` — se omiten
14404
- // las clases teñidas en vez de pisarlas, porque `bg-transparent` no borraría el
14405
- // `dark:bg-*` (grupo distinto para tailwind-merge).
14406
- hideIconWrapper ? "bg-transparent" : slots.iconWrapper, classNames?.iconWrapper), children: icon !== undefined && icon !== null ? (classNames?.alertIcon ? (
14407
- // `alertIcon` de v2 se aplicaba al propio SVG; aquí solo alcanza al icono
14408
- // personalizado (el de v3 por defecto se renderiza dentro del indicador).
14409
- jsxRuntime.jsx("span", { className: classNames.alertIcon, children: icon })) : (icon)) : undefined })), jsxRuntime.jsxs(react.Alert.Content, { className: react.cn("ms-2 box-border min-h-10 justify-center text-inherit", !hasContent && "items-center", classNames?.mainWrapper), children: [hasTitle && (
14410
- // v2: `text-small w-full font-medium block text-inherit leading-5`; v3 (`.alert__title`)
14411
- // pone `leading-6` y el color del estado — `text-inherit` devuelve el de v2.
14412
- jsxRuntime.jsx(react.Alert.Title, { className: react.cn("w-full leading-5 text-inherit", classNames?.title), children: title })), hasDescription && (
14413
- // v2: `pl-[1px] text-small font-normal text-inherit`; v3 (`.alert__description`) pone
14414
- // `text-muted`.
14415
- jsxRuntime.jsx(react.Alert.Description, { className: react.cn("pl-px font-normal text-inherit", slots.description, classNames?.description), children: description })), children] }), endContent, showClose && (jsxRuntime.jsx(react.CloseButton, { "aria-label": t.closeButtonAriaLabel, className: react.cn(CLOSE_BASE, slots.closeButton, classNames?.closeButton), onPress: handleClose }))] }));
14416
- };
14417
- AlertFacade.displayName = "Aurora.Alert";
14418
- /**
14419
- * `Alert` de Aurora: fachada v2 + estáticos compound de v3 (`Alert.Indicator`, `Alert.Content`,
14420
- * `Alert.Title`, `Alert.Description`), los mismos objetos que exporta `@heroui/react`.
14421
- */
14422
- const Alert = Object.assign(AlertFacade, {
14423
- Indicator: react.Alert.Indicator,
14424
- Content: react.Alert.Content,
14425
- Title: react.Alert.Title,
14426
- Description: react.Alert.Description,
14427
- });
14428
-
14429
- // TECH-2459 — Fachada `Checkbox` (hallazgo loginos 07-sep: `index.tsx` reexportaba el `Checkbox`
14430
- // CRUDO de v3, así que el JSX v2 de las apps — `isSelected` + `onValueChange` dentro de `Controller`
14431
- // de RHF en backoffice ×12, smbs ×23, agency ×5 — no compilaba pese a existir el símbolo).
14432
- //
14433
- // HeroUI v3 convierte el Checkbox en compound (`Checkbox.Content` > `Checkbox.Control` >
14434
- // `Checkbox.Indicator` + la etiqueta como children de `Content`), renombra `onValueChange` →
14435
- // `onChange` y ELIMINA `color`, `size`, `radius`, `lineThrough`, `icon`, `classNames` y
14436
- // `disableAnimation` (guía oficial `checkbox`). Su único eje propio es `variant`
14437
- // (`primary`/`secondary`), que NO es un color: `secondary` es un tratamiento gris de menor énfasis
14438
- // — el falso amigo de siempre (skill `auraui-design-tokens`).
14439
- //
14440
- // Esta fachada compone el compound por dentro y repone cada prop v2 con los literales del `tv`
14441
- // de v2 (`@heroui/theme@2.4.15` → `dist/components/checkbox.js`), traducidos a utilidades de
14442
- // Tailwind 4 sobre los tokens de Aurora. El relleno del estado marcado en v3 es el `::before` del
14443
- // control (`bg-accent`, = `--color-primary` vía F1), así que `color` se reimplementa pisando ese
14444
- // pseudo-elemento con `before:bg-<color>` — utilidad (capa `utilities`) que gana a la regla de
14445
- // `checkbox.css` (capa `components`), incluida la de hover (`bg-accent-hover`).
14446
- //
14447
- // ⚠️ Estados de react-aria, verificados en el navegador contra `checkbox.css`: `data-selected`/
14448
- // `data-indeterminate`/`data-invalid`/`data-disabled` viven en la RAÍZ (`.checkbox`);
14449
- // `data-hovered`/`data-pressed`/`data-focus-visible` en `[data-slot="checkbox-content"]`. Por
14450
- // eso hay dos grupos con nombre (`group/checkbox`, `group/content`) y NUNCA `data-[hover=true]`
14451
- // (trampa 3 de la skill: v2 escribía `data-hover`; react-aria escribe `data-hovered`).
14452
- /**
14453
- * Relleno del estado marcado (`::before` del control) y del indeterminado (fondo del control)
14454
- * por color. `primary` no pisa nada: el `bg-accent` de v3 YA es `--color-primary`. `default`
14455
- * usa el gris de Aurora (`--color-default`, = v2), nunca el `--default` crudo de v3 (D-F1-3).
14456
- *
14457
- * Literal v2: `after:bg-<color> after:text-<color>-foreground text-<color>-foreground`.
14458
- */
14459
- const CONTROL_COLOR = {
14460
- primary: "",
14461
- default: "before:bg-default group-data-[indeterminate=true]/checkbox:bg-default",
14462
- secondary: "before:bg-secondary group-data-[indeterminate=true]/checkbox:bg-secondary",
14463
- success: "before:bg-success group-data-[indeterminate=true]/checkbox:bg-success",
14464
- warning: "before:bg-warning group-data-[indeterminate=true]/checkbox:bg-warning",
14465
- danger: "before:bg-danger group-data-[indeterminate=true]/checkbox:bg-danger",
14466
- };
14467
- /**
14468
- * Color del glifo (check / línea). v3 lo fija en el SVG (`[data-slot="checkbox-default-indicator--
14469
- * checkmark"] { text-accent-foreground }`), así que hay que alcanzar al SVG, no al indicador.
14470
- */
14471
- const INDICATOR_COLOR = {
14472
- primary: "",
14473
- default: "[&_svg]:text-default-foreground",
14474
- secondary: "[&_svg]:text-secondary-foreground",
14475
- success: "[&_svg]:text-success-foreground",
14476
- warning: "[&_svg]:text-warning-foreground",
14477
- danger: "[&_svg]:text-danger-foreground",
14478
- };
14479
- // v2 (`checkbox.js`, variante `size`): wrapper `w-4/w-5/w-6` (16/20/24px), label
14480
- // `text-small/medium/large`, icon `w-3 h-2` / `w-4 h-3` / `w-5 h-4`. v3: control `size-4`, glifo
14481
- // `size-2.5`, texto `text-sm`, para todos.
14482
- const CONTROL_SIZE = {
14483
- sm: "size-4",
14484
- md: "size-5",
14485
- lg: "size-6",
14486
- };
14487
- const INDICATOR_SIZE = {
14488
- sm: "size-3 [&_svg]:size-2.5",
14489
- md: "size-3.5 [&_svg]:size-3",
14490
- lg: "size-4 [&_svg]:size-3.5",
14491
- };
14492
- const LABEL_SIZE = {
14493
- sm: "text-sm",
14494
- md: "text-base",
14495
- lg: "text-lg",
14496
- };
14497
- // v2: `rounded-[calc(theme(borderRadius.medium)*K)]` con `medium` = 12px → 6 / 7.2 / 8.4 px, y por
14498
- // defecto el radio lo decidía `size` (sm→0.5, md→0.6, lg→0.7). El `::before` (relleno) lleva el
14499
- // mismo radio porque aquí cubre el border-box completo (ver `CONTROL_BASE`).
14500
- const RADIUS_CLASS = {
14501
- none: "rounded-none before:rounded-none",
14502
- sm: "rounded-[6px] before:rounded-[6px]",
14503
- md: "rounded-[7.2px] before:rounded-[7.2px]",
14504
- lg: "rounded-[8.4px] before:rounded-[8.4px]",
14505
- full: "rounded-full before:rounded-full",
14506
- };
14507
- const RADIUS_BY_SIZE = {
14508
- sm: "sm",
14509
- md: "md",
14510
- lg: "lg",
14511
- };
14512
- /**
14513
- * Caja del control (slot `wrapper` de v2): borde de 2px en `--color-default` sin sombra
14514
- * (`before:border-2 before:border-default`), y el relleno cubriendo la caja ENTERA al marcarse.
14515
- *
14516
- * En v3 el borde vive en el propio control (1px, `border-field-border`, `shadow-field`) y el
14517
- * relleno es un `::before` a `inset-0`, que solo alcanza al padding-box: por eso v3 pone
14518
- * `border-transparent` al marcarse y el relleno queda 1px por dentro. Con el borde de 2px de v2
14519
- * eso dejaría un anillo de 2px: se lleva el `::before` al border-box (`-inset-0.5` = −2px) y se
14520
- * quita el `overflow-hidden` de v3 para que no lo recorte. El `::before` es un descendiente
14521
- * posicionado, así que pinta ENCIMA del borde y lo tapa, como el `after` de v2.
14522
- *
14523
- * Hover v2: `group-data-[hover=true]:before:bg-default-100` — tinte suave de la caja sin marcar
14524
- * (en v3 la caja es el fondo del control, no un pseudo). `data-hovered` lo emite `Checkbox.Content`.
14525
- *
14526
- * Indeterminado: v3 pinta el fondo del control (dentro del borde). Se enciende también el
14527
- * `::before` para que el relleno cubra la caja entera, igual que al marcar.
14528
- */
14529
- const CONTROL_BASE = [
14530
- "border-2 border-default shadow-none overflow-visible",
14531
- "before:-inset-0.5",
14532
- "group-data-[hovered=true]/content:bg-default-100",
14533
- "group-data-[indeterminate=true]/checkbox:before:scale-100",
14534
- "group-data-[indeterminate=true]/checkbox:before:opacity-100",
14535
- ].join(" ");
14536
- // v2 (`lineThrough: true`, slot `label`): la raya crece de 0 al 100% al marcarse y la etiqueta se
14537
- // atenúa al 60%. `data-selected` está en la raíz → `group-data-[…]/checkbox`.
14538
- const LINE_THROUGH = [
14539
- "inline-flex items-center justify-center",
14540
- "before:absolute before:h-0.5 before:w-0 before:bg-foreground before:content-['']",
14541
- "group-data-[selected=true]/checkbox:opacity-60",
14542
- "group-data-[selected=true]/checkbox:before:w-full",
14543
- ].join(" ");
14544
- // Los subcomponentes compound de v3, adjuntos como estáticos para que la API v3 siga funcionando
14545
- // a través de la fachada (`<Checkbox><Checkbox.Control>…`).
16307
+ };
16308
+ case "faded":
16309
+ return {
16310
+ base: `border ${FLAT_BASE[color]} ${FADED_BORDER[color]}`,
16311
+ description: defaultDescription,
16312
+ iconWrapper: `${ICON_WRAPPER_TINTED} ${FLAT_ICON_WRAPPER[color]}`,
16313
+ closeButton: FLAT_CLOSE[color],
16314
+ };
16315
+ default:
16316
+ return {
16317
+ base: FLAT_BASE[color],
16318
+ description: defaultDescription,
16319
+ iconWrapper: `${ICON_WRAPPER_TINTED} ${FLAT_ICON_WRAPPER[color]}`,
16320
+ closeButton: FLAT_CLOSE[color],
16321
+ };
16322
+ }
16323
+ }
16324
+ // Los subcomponentes compound de v3, adjuntos como estáticos (`Alert.Indicator`…) para que el JSX
16325
+ // v3 (loginos ya migró el suyo) siga compilando y renderizando IGUAL que contra el `Alert` crudo.
14546
16326
  const COMPOUND_PARTS = new Set([
14547
- react.Checkbox.Control,
14548
- react.Checkbox.Indicator,
14549
- react.Checkbox.Content,
16327
+ react.Alert.Indicator,
16328
+ react.Alert.Content,
16329
+ react.Alert.Title,
16330
+ react.Alert.Description,
14550
16331
  ]);
14551
- /** API compound de v3 detectada por hijos directos: la fachada no envuelve nada. */
16332
+ /**
16333
+ * Detecta la API compound de v3: si algún hijo directo es `Alert.Indicator`/`Content`/`Title`/
16334
+ * `Description`, el consumidor ya compone la estructura y la fachada NO debe envolver nada (si lo
16335
+ * hiciera, saldrían dos indicadores). `<Alert>Texto</Alert>` (sin partes compound) sigue siendo
16336
+ * API v2: children bajo la descripción, con icono y círculo, como en v2.
16337
+ */
14552
16338
  function hasCompoundChildren(children) {
14553
16339
  return $8WNJc$react.Children.toArray(children).some((child) => $8WNJc$react.isValidElement(child) && COMPOUND_PARTS.has(child.type));
14554
16340
  }
16341
+ /** `isEmpty` de v2 (`@heroui/shared-utils`): null/undefined/"" no cuentan como contenido. */
16342
+ function hasValue$1(node) {
16343
+ return node !== null && node !== undefined && node !== "" && node !== false;
16344
+ }
14555
16345
  /**
14556
- * Checkbox - Casilla basada en HeroUI v3 con la API pública de v2 intacta (TECH-2459).
16346
+ * Alert - Aviso contextual basado en HeroUI v3 con la API pública de v2 intacta (TECH-2459).
14557
16347
  *
14558
- * - **API v2**: `children` como etiqueta, `isSelected`/`defaultSelected` + `onValueChange`
14559
- * (también `onChange`), `color`, `size`, `radius`, `lineThrough`, `icon`, `classNames`. Funciona
14560
- * dentro de `Controller` de react-hook-form (`isSelected={value} onValueChange={onChange}`).
14561
- * - **API compound v3**: si los children traen `Checkbox.Control`/`Indicator`/`Content`, pasa tal
14562
- * cual a la raíz de HeroUI (con `onValueChange` seguido igualmente).
16348
+ * Dos modos, decididos por los `children`:
16349
+ * - **API v2** (`color`/`variant`/`title`/`description`/`isClosable`…): la fachada compone el
16350
+ * compound de v3 y repone `variant` (fondo teñido en `flat`), `radius`, el círculo del icono,
16351
+ * el botón de cerrar (`CloseButton` de v3 con `aria-label` traducido) y la visibilidad.
16352
+ * - **API compound v3** (`<Alert status="danger"><Alert.Indicator/><Alert.Content>…`): pasa tal
16353
+ * cual al `Alert` de HeroUI; `color`, si viene, se traduce a `status`.
14563
16354
  */
14564
- const CheckboxFacade = ({ children, onChange, onValueChange, color = "primary", size = "md", radius, lineThrough = false, icon, disableAnimation = false, isInvalid, classNames, className, ...rest }) => {
14565
- const handleChange = (isSelected) => {
14566
- onChange?.(isSelected);
14567
- onValueChange?.(isSelected);
14568
- };
16355
+ const AlertFacade = ({ color, status, variant = "flat", radius = "md", title, description, icon, hideIcon = false, hideIconWrapper = false, isClosable = false, onClose, isVisible: isVisibleProp, isDefaultVisible, onVisibleChange, startContent, endContent, classNames, className, children, translations = {}, ...rest }) => {
16356
+ // `useControlledState` de v2 (`use-alert.ts`): controlado si llega `isVisible`; si no, estado
16357
+ // propio que arranca en `isDefaultVisible ?? true` y se apaga al cerrar.
16358
+ const [uncontrolledVisible, setUncontrolledVisible] = $8WNJc$react.useState(isDefaultVisible ?? true);
16359
+ const isControlled = isVisibleProp !== undefined;
16360
+ const isVisible = isControlled ? isVisibleProp : uncontrolledVisible;
16361
+ const t = { ...DEFAULT_TRANSLATIONS, ...translations };
16362
+ // `status` (v3) gana sobre `color` (v2) para la clase de estado de HeroUI; y si SOLO llega
16363
+ // `status`, las utilidades de Aurora siguen el color equivalente (`accent` → `primary`).
16364
+ const effectiveColor = color ?? (status ? COLOR_BY_STATUS[status] : "default");
16365
+ const effectiveStatus = status ?? STATUS_BY_COLOR[effectiveColor];
16366
+ if (!isVisible) {
16367
+ return null;
16368
+ }
14569
16369
  if (hasCompoundChildren(children)) {
14570
- return (jsxRuntime.jsx(react.Checkbox, { ...rest, isInvalid: isInvalid, onChange: handleChange, className: react.cn(classNames?.base, className), children: children }));
16370
+ return (jsxRuntime.jsx(react.Alert, { ...rest, status: effectiveStatus, className: react.cn(classNames?.base, className), children: children }));
14571
16371
  }
14572
- const indicatorContent = typeof icon === "function"
14573
- ? (state) => icon({
14574
- isSelected: state.isSelected,
14575
- isIndeterminate: state.isIndeterminate,
14576
- isDisabled: state.isDisabled,
14577
- })
14578
- : icon;
14579
- const hasLabel = children !== undefined && children !== null && children !== false;
14580
- return (jsxRuntime.jsx(react.Checkbox, { ...rest, isInvalid: isInvalid, onChange: handleChange,
14581
- // Base v2: `group relative max-w-fit inline-flex items-center justify-start cursor-pointer
14582
- // p-2 -m-2 select-none` (el `p-2 -m-2` es el área de pulsado ampliada de v2). v3
14583
- // (`.checkbox`) usa `flex flex-col items-start` para apilar `Description`/`FieldError`,
14584
- // slots que esta fachada no renderiza; en **inline** la caja participa de la línea de
14585
- // texto, donde la esperan los consumidores (mismo criterio que la fachada `Switch`).
14586
- className: react.cn("group/checkbox relative -m-2 inline-flex max-w-fit flex-row items-center p-2", classNames?.base, className), children: jsxRuntime.jsxs(react.Checkbox.Content, { className: "group/content gap-2 font-normal", children: [jsxRuntime.jsx(react.Checkbox.Control, { className: react.cn(CONTROL_BASE, CONTROL_SIZE[size], RADIUS_CLASS[radius ?? RADIUS_BY_SIZE[size]], CONTROL_COLOR[color],
14587
- // v2 `isInvalid`: `before:border-danger` (el borde vivía en el pseudo).
14588
- isInvalid && "border-danger", disableAnimation && "transition-none before:transition-none", classNames?.wrapper), children: jsxRuntime.jsx(react.Checkbox.Indicator, { className: react.cn(INDICATOR_SIZE[size], INDICATOR_COLOR[color], disableAnimation && "[&_svg]:transition-none", classNames?.icon), children: indicatorContent }) }), hasLabel && (
14589
- // Slot `label` de v2: `relative text-foreground select-none` + tamaño de texto.
14590
- // `data-slot="label"` para que las reglas de cursor/selección de `.checkbox` de v3
14591
- // le alcancen igual que a un `<Label>`.
14592
- jsxRuntime.jsx("span", { "data-slot": "label", className: react.cn("relative select-none text-foreground", LABEL_SIZE[size], lineThrough && LINE_THROUGH, isInvalid && "text-danger", classNames?.label), children: children }))] }) }));
16372
+ const handleClose = () => {
16373
+ if (!isControlled) {
16374
+ setUncontrolledVisible(false);
16375
+ }
16376
+ onVisibleChange?.(false);
16377
+ onClose?.();
16378
+ };
16379
+ const slots = slotClasses(variant, effectiveColor);
16380
+ const hasTitle = hasValue$1(title);
16381
+ const hasDescription = hasValue$1(description);
16382
+ // `hasContent` de v2: sin descripción ni children, el título se centra verticalmente con el icono.
16383
+ const hasContent = hasDescription || hasValue$1(children);
16384
+ const showClose = isClosable || Boolean(onClose);
16385
+ return (jsxRuntime.jsxs(react.Alert, { role: "alert", "data-visible": "true", "data-closeable": showClose ? "true" : undefined, "data-has-title": hasTitle ? "true" : undefined, "data-has-description": hasDescription ? "true" : undefined, ...rest, status: effectiveStatus,
16386
+ // Base v2: `flex flex-grow flex-row w-full items-start py-3 px-4 gap-x-1`. v3 ya trae
16387
+ // flex/w-full/items-start/px-4/py-3; se repone el `gap-x-1` (v3: `gap-4`), se quita el
16388
+ // `shadow-surface` (v2 no tenía sombra en el base) y se aplica variant/radius.
16389
+ className: react.cn("grow gap-x-1 shadow-none", slots.base, RADIUS_CLASS[radius], hideIconWrapper && "gap-x-0", classNames?.base, className), children: [startContent, !hideIcon && (jsxRuntime.jsx(react.Alert.Indicator, { className: react.cn(ICON_WRAPPER_BASE,
16390
+ // v2 `hideIconWrapper`: `!bg-transparent !shadow-none !border-none` — se omiten
16391
+ // las clases teñidas en vez de pisarlas, porque `bg-transparent` no borraría el
16392
+ // `dark:bg-*` (grupo distinto para tailwind-merge).
16393
+ hideIconWrapper ? "bg-transparent" : slots.iconWrapper, classNames?.iconWrapper), children: icon !== undefined && icon !== null ? (classNames?.alertIcon ? (
16394
+ // `alertIcon` de v2 se aplicaba al propio SVG; aquí solo alcanza al icono
16395
+ // personalizado (el de v3 por defecto se renderiza dentro del indicador).
16396
+ jsxRuntime.jsx("span", { className: classNames.alertIcon, children: icon })) : (icon)) : undefined })), jsxRuntime.jsxs(react.Alert.Content, { className: react.cn("ms-2 box-border min-h-10 justify-center text-inherit", !hasContent && "items-center", classNames?.mainWrapper), children: [hasTitle && (
16397
+ // v2: `text-small w-full font-medium block text-inherit leading-5`; v3 (`.alert__title`)
16398
+ // pone `leading-6` y el color del estado — `text-inherit` devuelve el de v2.
16399
+ jsxRuntime.jsx(react.Alert.Title, { className: react.cn("w-full leading-5 text-inherit", classNames?.title), children: title })), hasDescription && (
16400
+ // v2: `pl-[1px] text-small font-normal text-inherit`; v3 (`.alert__description`) pone
16401
+ // `text-muted`.
16402
+ jsxRuntime.jsx(react.Alert.Description, { className: react.cn("pl-px font-normal text-inherit", slots.description, classNames?.description), children: description })), children] }), endContent, showClose && (jsxRuntime.jsx(react.CloseButton, { "aria-label": t.closeButtonAriaLabel, className: react.cn(CLOSE_BASE, slots.closeButton, classNames?.closeButton), onPress: handleClose }))] }));
14593
16403
  };
14594
- CheckboxFacade.displayName = "Aurora.Checkbox";
16404
+ AlertFacade.displayName = "Aurora.Alert";
14595
16405
  /**
14596
- * `Checkbox` de Aurora: fachada v2 + estáticos compound de v3 (`Checkbox.Control`,
14597
- * `Checkbox.Indicator`, `Checkbox.Content`), los mismos objetos que exporta `@heroui/react`.
16406
+ * `Alert` de Aurora: fachada v2 + estáticos compound de v3 (`Alert.Indicator`, `Alert.Content`,
16407
+ * `Alert.Title`, `Alert.Description`), los mismos objetos que exporta `@heroui/react`.
14598
16408
  */
14599
- const Checkbox = Object.assign(CheckboxFacade, {
14600
- Control: react.Checkbox.Control,
14601
- Indicator: react.Checkbox.Indicator,
14602
- Content: react.Checkbox.Content,
16409
+ const Alert = Object.assign(AlertFacade, {
16410
+ Indicator: react.Alert.Indicator,
16411
+ Content: react.Alert.Content,
16412
+ Title: react.Alert.Title,
16413
+ Description: react.Alert.Description,
14603
16414
  });
14604
16415
 
16416
+ const OMIT$1 = new Set(["children", "className", "type"]);
16417
+ const CONCAT = new Set(["aria-describedby", "aria-labelledby"]);
16418
+ function mergeRefs$1(...refs) {
16419
+ return (value) => {
16420
+ for (const ref of refs) {
16421
+ if (typeof ref === "function") {
16422
+ ref(value);
16423
+ }
16424
+ else if (ref) {
16425
+ ref.current = value;
16426
+ }
16427
+ }
16428
+ };
16429
+ }
16430
+ function mergeTriggerProps$1(trigger, child) {
16431
+ const merged = { ...trigger, ...child };
16432
+ for (const key of Object.keys(trigger)) {
16433
+ const a = trigger[key];
16434
+ const b = child[key];
16435
+ if (/^on[A-Z]/.test(key) &&
16436
+ typeof a === "function" &&
16437
+ typeof b === "function") {
16438
+ merged[key] = (...args) => {
16439
+ a(...args);
16440
+ b(...args);
16441
+ };
16442
+ }
16443
+ else if (CONCAT.has(key) && a && b) {
16444
+ merged[key] = `${a} ${b}`;
16445
+ }
16446
+ else if (b === undefined) {
16447
+ merged[key] = a;
16448
+ }
16449
+ }
16450
+ return merged;
16451
+ }
16452
+ /** ¿Se puede clonar como trigger? Un único elemento que no sea Fragment. */
16453
+ function isClonableTriggerChild(children) {
16454
+ return $8WNJc$react.isValidElement(children) && children.type !== $8WNJc$react.Fragment;
16455
+ }
16456
+ /**
16457
+ * `render` para `Dropdown.Trigger`: clona las props resueltas del `<button>` de v3 sobre `child`.
16458
+ * `triggerClassName` es el `className` que la app puso en el propio `DropdownTrigger`.
16459
+ */
16460
+ function renderDropdownTrigger(child, triggerClassName) {
16461
+ return (buttonProps) => {
16462
+ const { ref, ...rest } = buttonProps;
16463
+ const triggerProps = {};
16464
+ for (const [key, value] of Object.entries(rest)) {
16465
+ if (OMIT$1.has(key) || key.startsWith("data-")) {
16466
+ continue;
16467
+ }
16468
+ triggerProps[key] = value;
16469
+ }
16470
+ const hostTag = typeof child.type === "string" ? child.type : null;
16471
+ if (hostTag === "button") {
16472
+ triggerProps.type = rest.type;
16473
+ }
16474
+ else if (hostTag && hostTag !== "a") {
16475
+ triggerProps.role = "button";
16476
+ triggerProps.tabIndex = 0;
16477
+ }
16478
+ const childRef = (Number.parseInt($8WNJc$react.version, 10) < 19
16479
+ ? child.ref
16480
+ : child.props.ref);
16481
+ const childClass = child.props.className;
16482
+ const className = triggerClassName && typeof childClass !== "function"
16483
+ ? react.cn(triggerClassName, childClass)
16484
+ : childClass;
16485
+ return $8WNJc$react.cloneElement(child, {
16486
+ ...mergeTriggerProps$1(triggerProps, child.props),
16487
+ ...(className === undefined ? {} : { className }),
16488
+ ref: mergeRefs$1(childRef, ref),
16489
+ });
16490
+ };
16491
+ }
16492
+
14605
16493
  // TECH-2459 — Fachada `Dropdown` / `DropdownSection`: repone la CABECERA DE SECCIÓN que la
14606
16494
  // migración a v3 dejó sin vía legal desde una app.
14607
16495
  //
@@ -14742,15 +16630,153 @@ const OWN_POPOVER = new Set([
14742
16630
  function isPart$1(node, parts) {
14743
16631
  return $8WNJc$react.isValidElement(node) && parts.has(node.type);
14744
16632
  }
14745
- const DropdownFacade = ({ children, ...props }) => {
14746
- const nodes = $8WNJc$react.Children.toArray(children);
16633
+ function isFragment(node) {
16634
+ return $8WNJc$react.isValidElement(node) && node.type === $8WNJc$react.Fragment;
16635
+ }
16636
+ /**
16637
+ * `Children.toArray` aplana arrays pero NO Fragments (un `<>…</>` cuenta como UN nodo). Esto baja
16638
+ * por ellos, y prefija la `key` de cada hijo con la del Fragment para que dos Fragments hermanos
16639
+ * no produzcan claves repetidas (`toArray` numera cada nivel desde `.0`).
16640
+ */
16641
+ function flatten(children, prefix = "") {
16642
+ return $8WNJc$react.Children.toArray(children).flatMap((node) => {
16643
+ if (isFragment(node)) {
16644
+ return flatten(node.props.children, `${prefix}${String(node.key)}`);
16645
+ }
16646
+ if (prefix && $8WNJc$react.isValidElement(node)) {
16647
+ return [$8WNJc$react.cloneElement(node, { key: `${prefix}${String(node.key)}` })];
16648
+ }
16649
+ return [node];
16650
+ });
16651
+ }
16652
+ /**
16653
+ * Mapea el árbol conservando su forma: atraviesa Fragments (se reconstruyen con sus hijos
16654
+ * mapeados) y aplica `fn` al resto de nodos. Para el caso en el que el popover es del consumidor:
16655
+ * no hay que mover nada de sitio, solo completar props.
16656
+ */
16657
+ function mapTree(children, fn) {
16658
+ return $8WNJc$react.Children.toArray(children).map((node) => isFragment(node)
16659
+ ? $8WNJc$react.cloneElement(node, undefined, ...mapTree(node.props.children, fn))
16660
+ : fn(node));
16661
+ }
16662
+ /**
16663
+ * Traducción de colocación v2 → v3, copiada del oráculo, no deducida: `@heroui/aria-utils@2.2.29`
16664
+ * → `dist/chunk-WQVQ7P2I.mjs:53-69` (`toReactAriaPlacement`), que es lo que el `Popover` de v2
16665
+ * hacía con la prop antes de dársela a react-aria. Ojo a los laterales: `left-start` NO es
16666
+ * `"left start"` (no existe en react-aria) sino `"left top"`.
16667
+ */
16668
+ const V2_TO_V3_PLACEMENT = {
16669
+ top: "top",
16670
+ bottom: "bottom",
16671
+ left: "left",
16672
+ right: "right",
16673
+ "top-start": "top start",
16674
+ "top-end": "top end",
16675
+ "bottom-start": "bottom start",
16676
+ "bottom-end": "bottom end",
16677
+ "left-start": "left top",
16678
+ "left-end": "left bottom",
16679
+ "right-start": "right top",
16680
+ "right-end": "right bottom",
16681
+ };
16682
+ /** v2 → v3; lo que ya viene en sintaxis de v3 pasa tal cual. */
16683
+ function toV3Placement(placement) {
16684
+ return (V2_TO_V3_PLACEMENT[placement] ??
16685
+ placement);
16686
+ }
16687
+ /** Solo las claves que el consumidor escribió: `undefined` no pisa nada. */
16688
+ function definedOnly(values) {
16689
+ return Object.fromEntries(Object.entries(values).filter(([, value]) => value !== undefined));
16690
+ }
16691
+ /**
16692
+ * `closeOnSelect` de v2 → `shouldCloseOnSelect` del `Menu` de v3. Precedencia, la de v2
16693
+ * (`@heroui/dropdown@2.3.32` → `dist/chunk-HT5XYLWZ.mjs`, `getCloseOnSelect`): lo del menú gana a
16694
+ * lo del root. Del menú se lee `shouldCloseOnSelect` (v3) y, si no, `closeOnSelect` (v2: 6 de los
16695
+ * 20 `<DropdownMenu>` de beweossmbs lo escriben en el MENÚ, no en el root, y el `Menu` de v3 lo
16696
+ * ignora — sin esto, un multiselect se cerraba en cada clic). El `closeOnSelect` se retira del
16697
+ * elemento: v3 no lo conoce.
16698
+ */
16699
+ function withCloseOnSelect(node, rootCloseOnSelect) {
16700
+ if (!isPart$1(node, INSIDE_POPOVER)) {
16701
+ return node;
16702
+ }
16703
+ const menu = node;
16704
+ const { closeOnSelect, shouldCloseOnSelect } = menu.props;
16705
+ const resolved = shouldCloseOnSelect ?? closeOnSelect ?? rootCloseOnSelect;
16706
+ if (resolved === undefined && !("closeOnSelect" in menu.props)) {
16707
+ return node;
16708
+ }
16709
+ const { closeOnSelect: _dropped, ...rest } = menu.props;
16710
+ return $8WNJc$react.createElement(menu.type, {
16711
+ ...rest,
16712
+ key: menu.key,
16713
+ shouldCloseOnSelect: resolved,
16714
+ });
16715
+ }
16716
+ /**
16717
+ * `<DropdownTrigger><Button/></DropdownTrigger>` de v2 → UN botón, el de la app, con las props del
16718
+ * trigger de v3 clonadas encima vía su `render` (ver `_internal/renderTrigger.tsx`). Sin esto v3
16719
+ * pinta dos `<button>` anidados. Se deja el trigger de v3 tal cual si el consumidor ya pasa su
16720
+ * propio `render`, o si el hijo no es UN elemento (texto, Fragment, función de render props):
16721
+ * entonces el `<button>` de v3 es el trigger, que es lo correcto.
16722
+ *
16723
+ * Medido en beweossmbs `origin/staging` (f6d5be1c2): 20 `<DropdownTrigger>`, 19 con un `<Button>`
16724
+ * de hijo directo y 1 con `{trigger || defaultTrigger}` (también un `Button` en ejecución).
16725
+ */
16726
+ function withClonedTrigger(node) {
16727
+ if (!$8WNJc$react.isValidElement(node) || node.type !== react.Dropdown.Trigger) {
16728
+ return node;
16729
+ }
16730
+ const trigger = node;
16731
+ const { children, render, className } = trigger.props;
16732
+ if (render !== undefined || !isClonableTriggerChild(children)) {
16733
+ return node;
16734
+ }
16735
+ return $8WNJc$react.cloneElement(trigger, {
16736
+ render: renderDropdownTrigger(children, typeof className === "string" ? className : undefined),
16737
+ });
16738
+ }
16739
+ const DropdownFacade = ({ children, placement, offset, crossOffset, shouldFlip, containerPadding, closeOnSelect, shouldCloseOnScroll: _noEffectInV3, className, classNames, ...props }) => {
16740
+ const overrides = definedOnly({
16741
+ placement: placement === undefined ? undefined : toV3Placement(placement),
16742
+ offset,
16743
+ crossOffset,
16744
+ shouldFlip,
16745
+ containerPadding,
16746
+ });
16747
+ // v2: `base` (caja exterior), luego `content` + `className` (caja interior); en v3 es una sola
16748
+ // caja, así que van juntas y en ese orden. `cn` funde conflictos entre ellas; frente a la clase
16749
+ // de v3 (`.dropdown__popover`, capa `components`) cualquier utilidad ya gana por capa.
16750
+ const popoverClass = react.cn(classNames?.base, classNames?.content, className) || undefined;
16751
+ const menuWithClose = (node) => withCloseOnSelect(node, closeOnSelect);
16752
+ const nodes = flatten(children);
16753
+ // El popover es del consumidor: se respeta su árbol entero y se le COMPLETA lo que no dijo
16754
+ // (lo que sí dijo manda). Los menús dentro de él reciben el `closeOnSelect` igualmente.
16755
+ if (nodes.some((node) => isPart$1(node, OWN_POPOVER))) {
16756
+ const fillPopover = (node) => {
16757
+ if (!$8WNJc$react.isValidElement(node) || node.type !== react.Dropdown.Popover) {
16758
+ return node;
16759
+ }
16760
+ const own = node;
16761
+ const missing = Object.fromEntries(Object.entries(overrides).filter(([key]) => own.props[key] === undefined));
16762
+ const ownClass = own.props.className;
16763
+ return $8WNJc$react.cloneElement(own, {
16764
+ ...missing,
16765
+ ...(popoverClass && typeof ownClass !== "function"
16766
+ ? { className: react.cn(popoverClass, ownClass) }
16767
+ : {}),
16768
+ }, ...mapTree(own.props.children, menuWithClose));
16769
+ };
16770
+ return (jsxRuntime.jsx(react.Dropdown, { ...props, children: mapTree(children, (node) => fillPopover(withClonedTrigger(node))) }));
16771
+ }
14747
16772
  const menus = nodes.filter((node) => isPart$1(node, INSIDE_POPOVER));
14748
- // Nada que envolver (sin hijos, `children` como función, un popover ya montado a mano, o
14749
- // simplemente ningún menú suelto): el root tal cual, byte a byte como el crudo de v3.
14750
- if (menus.length === 0 || nodes.some((node) => isPart$1(node, OWN_POPOVER))) {
14751
- return jsxRuntime.jsx(react.Dropdown, { ...props, children: children });
16773
+ // Nada que envolver (sin hijos o ningún menú suelto): el root tal cual, como el crudo de v3.
16774
+ if (menus.length === 0) {
16775
+ return (jsxRuntime.jsx(react.Dropdown, { ...props, children: mapTree(children, withClonedTrigger) }));
14752
16776
  }
14753
- return (jsxRuntime.jsxs(react.Dropdown, { ...props, children: [nodes.filter((node) => !isPart$1(node, INSIDE_POPOVER)), jsxRuntime.jsx(react.Dropdown.Popover, { offset: 7, placement: "bottom", children: menus })] }));
16777
+ return (jsxRuntime.jsxs(react.Dropdown, { ...props, children: [nodes
16778
+ .filter((node) => !isPart$1(node, INSIDE_POPOVER))
16779
+ .map(withClonedTrigger), jsxRuntime.jsx(react.Dropdown.Popover, { offset: 7, placement: "bottom", ...overrides, className: popoverClass, children: menus.map(menuWithClose) })] }));
14754
16780
  };
14755
16781
  DropdownFacade.displayName = "Aurora.Dropdown";
14756
16782
  /**
@@ -14793,9 +16819,12 @@ const Dropdown = Object.assign(DropdownFacade, {
14793
16819
  // | beweosagency | 5 | 5 | 5 | 14 | **0** | la seleccionada sin píldora |
14794
16820
  // | backoffice-beweos | 5 | 5 | 5 | 18 | 18 (a mano) | bien |
14795
16821
  // | beweanalytics | 3 | 3 | 3 | 8 | 8 (a mano) | bien |
14796
- // | beweossmbs / loginos / linda-business | 0 | 0 | 0 | 0 | 0 | no usan tabs |
16822
+ // | beweossmbs / loginos / linda-business | 0 | 0 | 0 | 0 | 0 | no usan la forma v3 |
14797
16823
  //
14798
16824
  // (conteo de tags de APERTURA; beweanalytics escribe `Tab` suelto en vez de `Tabs.Tab`.)
16825
+ // Corrección del 23-sep: esa última fila solo contaba la forma v3. Las apps SIN migrar sí usan
16826
+ // tabs, y todas en la forma v2 (`<Tab key title>` suelto): beweossmbs 42 raíces, chatbot-claw 1,
16827
+ // linda-business 1. Esa forma la cubre la rama v2 del root, más abajo.
14799
16828
  // Dos lecturas que salen de esta tabla y que deciden el diseño:
14800
16829
  // · **26 indicadores montados a mano** contra 14 que faltan. La idempotencia no es un adorno:
14801
16830
  // sin ella esas 26 pestañas se quedarían con DOS indicadores.
@@ -14900,7 +16929,12 @@ function hasIndicator(children) {
14900
16929
  return node.type === $8WNJc$react.Fragment && hasIndicator(node.props.children);
14901
16930
  });
14902
16931
  }
14903
- const TabFacade = ({ children, ...props }) => {
16932
+ const TabFacade = ({ children: ownChildren, title,
16933
+ // `titleValue` es de v2 y no tiene destino en v3: se consume para que no llegue al DOM.
16934
+ titleValue: _titleValue, ...props }) => {
16935
+ // En la forma v3 la etiqueta son los `children`; si solo viene el `title` de v2 (un `Tab` con
16936
+ // `title` dentro de `Tabs.List`), hace de etiqueta. Con los dos, mandan los `children`.
16937
+ const children = ownChildren ?? title;
14904
16938
  // `children` como función es la forma de render props de react-aria (`TabRenderProps`). No se
14905
16939
  // puede inspeccionar, y menos aún combinar con un hermano —React rechaza una función como
14906
16940
  // hijo junto a un elemento—, así que se pasa tal cual. Un consumidor que la use monta el
@@ -14941,7 +16975,179 @@ const Tab = TabFacade;
14941
16975
  const INSIDE_CONTAINER = new Set([react.Tabs.List]);
14942
16976
  /** Si el consumidor ya monta el contenedor, la fachada no envuelve nada. */
14943
16977
  const OWN_CONTAINER = new Set([react.Tabs.ListContainer]);
14944
- const TabsFacade = ({ children, ...props }) => {
16978
+ // ---------------------------------------------------------------------------------------------
16979
+ // Forma v2: `<Tabs …><Tab key="x" title="X">panel</Tab></Tabs>`
16980
+ // ---------------------------------------------------------------------------------------------
16981
+ //
16982
+ // Las apps que faltan por migrar escriben SOLO esta forma (inventario con el parser de TypeScript
16983
+ // sobre `git show`, 23-sep: beweossmbs `origin/staging` 42 raíces, chatbot-claw
16984
+ // `origin/feat/TECH-3586/linda-claw` 1, linda-business `origin/staging` 1 — 44 raíces, 97 `Tab`,
16985
+ // todos con `key` y `title`, 39 con hijos = panel, 0 `Tabs.List`). En v3 esa forma no monta:
16986
+ // `Tab === Tabs.Tab` es un item de colección y fuera de `Tabs.List` react-aria lanza «cannot be
16987
+ // rendered outside a collection». La fachada la reescribe a la forma COMPLETA de v3:
16988
+ //
16989
+ // Tabs (root)
16990
+ // ├─ div data-slot="tabs-base" ← el `base` de v2: `classNames.base` + `className`
16991
+ // │ └─ Tabs.ListContainer ← el carril (sin él la píldora no se ve, ver arriba)
16992
+ // │ └─ Tabs.List aria-label del root ← `classNames.tabList`, `role="tablist"`
16993
+ // │ └─ Tabs.Tab id = key del Tab ← `classNames.tab` + `className` del Tab
16994
+ // │ ├─ Tabs.Indicator ← `classNames.cursor` (UNO por tab; v3 solo pinta el activo)
16995
+ // │ └─ title
16996
+ // └─ Tabs.Panel id = key (solo los Tab con hijos) ← `classNames.panel` + `className` del Tab
16997
+ //
16998
+ // Oráculo v2: `@heroui/tabs@2.2.29` `dist/tabs.js` y `@heroui/theme@2.4.15`
16999
+ // `dist/components/tabs.js` (npm pack, no de memoria). De ahí sale cada destino:
17000
+ // · `className` del root → `slots.base` junto a `classNames.base` (`tabs.js:88,114`), y `base`
17001
+ // envuelve SOLO la tira: los paneles son sus hermanos (`tabs.js:414-430`).
17002
+ // · `className` del Tab → la pestaña (`cn(classNames.tab, className)`, `tabs.js:207`) Y su panel
17003
+ // (`cn(classNames.panel, …, selectedItem.props.className)`, `tabs.js:282`).
17004
+ // · `classNames.cursor` → el `<span>` de la píldora (`tabs.js:145`), que en v3 es el indicador.
17005
+ // · Un Tab sin hijos NO genera panel (`if (!content) return null`, `tabs.js:284`).
17006
+ //
17007
+ // Las clases de la app van SIEMPRE en última posición de un `cn` (tailwind-merge): el `tv` de v2
17008
+ // corría con `twMerge: true`, así que su clase borraba la de la librería, y aquí igual — la
17009
+ // concatenación simple es el defecto ya medido en otras fachadas (la clase de la app perdía).
17010
+ /** Los dos nombres por los que una app llega a un `Tab`: la fachada y, por si acaso, el crudo. */
17011
+ const TAB_TYPES = new Set([TabFacade, react.Tabs.Tab]);
17012
+ /** Presencia de cualquiera de estas piezas = la app ya escribe la forma v3. */
17013
+ const V3_LIST_TYPES = new Set([
17014
+ react.Tabs.List,
17015
+ react.Tabs.ListContainer,
17016
+ ]);
17017
+ /**
17018
+ * Los `Tab` sueltos del root, en orden, **con su `key` original**.
17019
+ *
17020
+ * Se recorre con `Children.forEach` y no con `Children.toArray` a propósito: `toArray` REESCRIBE
17021
+ * las claves (`"all"` → `".$all"`) y la clave es justo lo que se necesita, porque en v2 es el
17022
+ * identificador que recibe `onSelectionChange` y que la app compara con su `selectedKey`.
17023
+ * `forEach` entrega cada hijo tal cual (comprobado: `key` intacta) y aplana arrays, o sea los
17024
+ * `.map()` y los `{cond && <Tab/>}` (los `false`/`null` se descartan). Los Fragments no los
17025
+ * aplana nadie, así que se desciende a mano — como en `hasIndicator`.
17026
+ */
17027
+ function collectV2Tabs(children, out = []) {
17028
+ $8WNJc$react.Children.forEach(children, (node) => {
17029
+ if (!$8WNJc$react.isValidElement(node)) {
17030
+ return;
17031
+ }
17032
+ if (node.type === $8WNJc$react.Fragment) {
17033
+ collectV2Tabs(node.props.children, out);
17034
+ }
17035
+ else if (TAB_TYPES.has(node.type)) {
17036
+ out.push(node);
17037
+ }
17038
+ });
17039
+ return out;
17040
+ }
17041
+ /** ¿Hay un `Tabs.List` / `Tabs.ListContainer` entre los hijos (descendiendo por Fragments)? */
17042
+ function hasV3List(children) {
17043
+ let found = false;
17044
+ $8WNJc$react.Children.forEach(children, (node) => {
17045
+ if (found || !$8WNJc$react.isValidElement(node)) {
17046
+ return;
17047
+ }
17048
+ found =
17049
+ V3_LIST_TYPES.has(node.type) ||
17050
+ (node.type === $8WNJc$react.Fragment && hasV3List(node.props.children));
17051
+ });
17052
+ return found;
17053
+ }
17054
+ /** v2 `underlined` es la línea inferior → `secondary` de v3; el resto de v2 → `primary` (default). */
17055
+ function toV3Variant(variant) {
17056
+ if (variant === "primary" || variant === "secondary") {
17057
+ return variant;
17058
+ }
17059
+ return variant === "underlined" ? "secondary" : undefined;
17060
+ }
17061
+ /** ¿El `Tab` de v2 trae contenido de panel? Mismo criterio que `if (!content)` de v2. */
17062
+ function hasPanelContent(children) {
17063
+ return typeof children === "function" || $8WNJc$react.Children.count(children) > 0;
17064
+ }
17065
+ const TabsFacade = (allProps) => {
17066
+ const { children, classNames, className, variant, fullWidth, items, isVertical, placement, orientation, destroyInactiveTabPanel = true,
17067
+ // 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;
17069
+ const v3Variant = toV3Variant(variant);
17070
+ const v3Orientation = orientation ??
17071
+ (isVertical || placement === "start" || placement === "end"
17072
+ ? "vertical"
17073
+ : undefined);
17074
+ // Forma v2 dinámica: `items` + `children` como función `(item) => <Tab key=…/>`.
17075
+ const dynamic = typeof children === "function";
17076
+ const v2Tabs = dynamic
17077
+ ? Array.from(items ?? [], (item, index) => {
17078
+ const element = children(item);
17079
+ const fallback = item.key ?? item.id ?? index;
17080
+ return element.key == null
17081
+ ? { element, key: String(fallback) }
17082
+ : { element, key: element.key };
17083
+ })
17084
+ : hasV3List(children)
17085
+ ? []
17086
+ : collectV2Tabs(children).map((element, index) => ({
17087
+ element,
17088
+ key: element.key ?? String(index),
17089
+ }));
17090
+ if (v2Tabs.length > 0) {
17091
+ const { "aria-label": ariaLabel, "aria-labelledby": ariaLabelledby, "aria-describedby": ariaDescribedby, ...rootProps } = rest;
17092
+ const tabs = v2Tabs.map(({ element, key }) => {
17093
+ const { id, title, titleValue: _titleValue, className: tabClassName, children: panel, ...tabProps } = element.props;
17094
+ return {
17095
+ id: id ?? key,
17096
+ title,
17097
+ tabClassName,
17098
+ panel,
17099
+ tabProps,
17100
+ };
17101
+ });
17102
+ // EL `base` DE v2: un div que envuelve SOLO la tira. Ahí van `className`, `classNames.base` y
17103
+ // el `w-full` de `fullWidth`, no al root. En v2 los paneles eran HERMANOS de `base`, fuera de
17104
+ // él (`tabs.js`: `<div {...getBaseProps()}><tabList/></div>` y luego los paneles), y en v3
17105
+ // el root envuelve tira + paneles. Mandarlas al root cambiaba dos pantallas de beweossmbs
17106
+ // sin que la app tocara nada: `service-detail.page.tsx` (`mb-4` debajo del bloque en vez de
17107
+ // entre tira y panel) y `whatsapp-campaign-full.page.tsx` (`flex justify-center` dejaba de
17108
+ // centrar la tira).
17109
+ //
17110
+ // Por qué un div propio y no el `Tabs.ListContainer`: el contenedor ES el carril gris, así
17111
+ // que un `w-full` o un `justify-center` de la app lo estirarían a todo el ancho. En v2 esa
17112
+ // clase caía en un envoltorio `inline-flex` y la tira (el carril de v2) seguía midiendo su
17113
+ // contenido. Este div es `flex`: el carril es un item de fila y mide su contenido, igual.
17114
+ // El scroller sigue funcionando por el `max-width: 100%` de `neutralize-v3.css`, que acota
17115
+ // el mínimo automático del item.
17116
+ //
17117
+ // El precio de meter un nodo entre el root y el carril: TODAS las reglas de la variante
17118
+ // `secondary` de v3 cuelgan de un combinador de hijo DIRECTO
17119
+ // (`.tabs--secondary > .tabs__list-container` y
17120
+ // `.tabs--secondary[data-orientation=…] > .tabs__list-container`, `tabs.css:234-282`), y
17121
+ // 5 pantallas de smbs llegan a ella por `variant="underlined"`. Por eso el div repite el
17122
+ // modificador y el `data-orientation` del root: esos selectores casan con div > carril (el
17123
+ // test lo comprueba con `matches()` sobre los selectores literales). `.tabs--secondary` no
17124
+ // tiene reglas propias, solo las de hijo directo, así que repetirlo no pinta nada en el div.
17125
+ // La única regla que deja de aplicarse es
17126
+ // `.tabs[data-orientation="horizontal"] > .tabs__list-container { align-self: start }` de
17127
+ // neutralize, que no hace falta: dentro de una fila el carril ya mide su contenido.
17128
+ 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 },
17130
+ jsxRuntime.jsx(react.Tabs.Indicator, { className: react.cn(classNames?.cursor) }),
17131
+ classNames?.tabContent ? (jsxRuntime.jsx("span", { className: classNames.tabContent, children: title })) : (title)))) }) }) }), tabs
17132
+ .filter(({ panel }) => hasPanelContent(panel))
17133
+ .map(({ id, panel, tabClassName }) => (jsxRuntime.jsx(react.Tabs.Panel, { className: react.cn(classNames?.panel, tabClassName), id: id, shouldForceMount: !destroyInactiveTabPanel || undefined, children: typeof panel === "function" ? null : panel }, id)))] }));
17134
+ }
17135
+ // Forma v3 (o nada que traducir): el camino de siempre. Solo se traducen las props de v2 que
17136
+ // tienen equivalente, para que una app a medio migrar no las pierda.
17137
+ // Sin `fullWidth` ni `classNames.base` el `className` pasa INTACTO (sin `cn`), igual que antes
17138
+ // de existir la forma v2: `cn` deduplica y reordena, y aquí no hay nada con qué fusionarlo.
17139
+ const v3Children = dynamic ? null : children;
17140
+ const v3Props = {
17141
+ ...rest,
17142
+ className: fullWidth || classNames?.base
17143
+ ? react.cn(fullWidth && "w-full", classNames?.base, className)
17144
+ : className,
17145
+ orientation: v3Orientation,
17146
+ variant: v3Variant,
17147
+ };
17148
+ return jsxRuntime.jsx(TabsV3, { ...v3Props, children: v3Children });
17149
+ };
17150
+ const TabsV3 = ({ children, ...props }) => {
14945
17151
  const nodes = $8WNJc$react.Children.toArray(children);
14946
17152
  const needsContainer = nodes.some((node) => isPart(node, INSIDE_CONTAINER)) &&
14947
17153
  !nodes.some((node) => isPart(node, OWN_CONTAINER));
@@ -15004,6 +17210,87 @@ const Tabs = Object.assign(TabsFacade, {
15004
17210
  Tab: TabFacade,
15005
17211
  });
15006
17212
 
17213
+ const OMIT = new Set([
17214
+ "role",
17215
+ "tabIndex",
17216
+ "className",
17217
+ "data-slot",
17218
+ "children",
17219
+ ]);
17220
+ function mergeRefs(...refs) {
17221
+ return (value) => {
17222
+ for (const ref of refs) {
17223
+ if (typeof ref === "function") {
17224
+ ref(value);
17225
+ }
17226
+ else if (ref) {
17227
+ ref.current = value;
17228
+ }
17229
+ }
17230
+ };
17231
+ }
17232
+ /** `mergeProps` de v2: handlers `on*` encadenados (trigger → hijo), el resto gana el hijo. */
17233
+ function mergeTriggerProps(trigger, child) {
17234
+ const merged = { ...trigger, ...child };
17235
+ for (const key of Object.keys(trigger)) {
17236
+ const a = trigger[key];
17237
+ const b = child[key];
17238
+ if (/^on[A-Z]/.test(key) &&
17239
+ typeof a === "function" &&
17240
+ typeof b === "function") {
17241
+ merged[key] = (...args) => {
17242
+ a(...args);
17243
+ b(...args);
17244
+ };
17245
+ }
17246
+ else if (key === "aria-describedby" && a && b) {
17247
+ merged[key] = `${a} ${b}`;
17248
+ }
17249
+ else if (b === undefined) {
17250
+ merged[key] = a;
17251
+ }
17252
+ }
17253
+ return merged;
17254
+ }
17255
+ function renderTooltipTrigger(children) {
17256
+ return (divProps) => {
17257
+ const { ref, ...rest } = divProps;
17258
+ const triggerProps = {};
17259
+ for (const [key, value] of Object.entries(rest)) {
17260
+ if (!OMIT.has(key)) {
17261
+ triggerProps[key] = value;
17262
+ }
17263
+ }
17264
+ const single = $8WNJc$react.Children.count(children) === 1 && $8WNJc$react.isValidElement(children);
17265
+ if (single && children.type !== $8WNJc$react.Fragment) {
17266
+ const child = children;
17267
+ const childRef = (Number.parseInt($8WNJc$react.version, 10) < 19
17268
+ ? child.ref
17269
+ : child.props.ref);
17270
+ return $8WNJc$react.cloneElement(child, {
17271
+ ...mergeTriggerProps(triggerProps, child.props),
17272
+ ref: mergeRefs(childRef, ref),
17273
+ });
17274
+ }
17275
+ return (jsxRuntime.jsx("div", { ...triggerProps, ref: ref, className: rest.className, "data-slot": "tooltip-trigger", children: children }));
17276
+ };
17277
+ }
17278
+
17279
+ /**
17280
+ * `color` de v2 (`@heroui/theme@2.4.15`, `popover` → `colorVariants.solid`): fondo y texto del
17281
+ * color en la caja, y la flecha del mismo color (v2 la pintaba con `before:bg-*`; en v3 es un SVG
17282
+ * con `fill: var(--overlay)`, así que se pisa su `fill`). `default` no añade nada: se ve como v3
17283
+ * (§9.1). Clases LITERALES — Tailwind 4 solo genera lo que está escrito en el fuente.
17284
+ */
17285
+ const COLOR_CLASSES = {
17286
+ default: "",
17287
+ foreground: "bg-foreground text-background [&_[data-slot=overlay-arrow]]:fill-foreground",
17288
+ primary: "bg-primary text-primary-foreground [&_[data-slot=overlay-arrow]]:fill-primary",
17289
+ secondary: "bg-secondary text-secondary-foreground [&_[data-slot=overlay-arrow]]:fill-secondary",
17290
+ success: "bg-success text-success-foreground [&_[data-slot=overlay-arrow]]:fill-success",
17291
+ warning: "bg-warning text-warning-foreground [&_[data-slot=overlay-arrow]]:fill-warning",
17292
+ danger: "bg-danger text-danger-foreground [&_[data-slot=overlay-arrow]]:fill-danger",
17293
+ };
15007
17294
  /**
15008
17295
  * Tooltip - Componente BeweOS basado en HeroUI.
15009
17296
  *
@@ -15019,7 +17306,11 @@ const Tabs = Object.assign(TabsFacade, {
15019
17306
  const Tooltip = ({ enableTapOnTouch = true, touchPlacement = "top", placement, offset, showArrow = false, content, isOpen, defaultOpen, onOpenChange, isDisabled,
15020
17307
  // v3 cambia el default de 0 (v2) a 700 — se mantiene 0 para no regresionar el
15021
17308
  // comportamiento instantáneo que ya tenía v2 (ver Tooltip.types.ts).
15022
- delay = 0, closeDelay, trigger, className, children, }) => {
17309
+ delay = 0, closeDelay, trigger, className, classNames, color = "default", children, }) => {
17310
+ // v3 tiene un solo nodo donde v2 tenía `base` + `content` (ver `TooltipClassNames`). Orden de
17311
+ // v2: color → `classNames` → `className`; `cn` fusiona para que la clase de la app gane.
17312
+ const contentClassName = react.cn(COLOR_CLASSES[color] ?? "", classNames?.base, classNames?.content, className);
17313
+ const arrow = showArrow && (jsxRuntime.jsx(react.Tooltip.Arrow, { className: classNames?.arrow }));
15023
17314
  // Detección táctil: NO basta con `(hover: none)`. Muchos móviles (p.ej.
15024
17315
  // Android) reportan `(hover: hover)` y `(hover: none)` da false, dejando el
15025
17316
  // tap sin activar. Se combina con `(pointer: coarse)` (coincide si cualquiera
@@ -15057,7 +17348,7 @@ delay = 0, closeDelay, trigger, className, children, }) => {
15057
17348
  }, [tapMode, open, setOpen]);
15058
17349
  // Desktop (o tap deshabilitado) → comportamiento nativo de HeroUI.
15059
17350
  if (!tapMode) {
15060
- return (jsxRuntime.jsxs(react.Tooltip, { isOpen: isOpen, defaultOpen: defaultOpen, onOpenChange: onOpenChange, isDisabled: isDisabled, delay: delay, closeDelay: closeDelay, trigger: trigger, children: [jsxRuntime.jsx(react.Tooltip.Trigger, { children: children }), jsxRuntime.jsxs(react.Tooltip.Content, { placement: placement, offset: offset, showArrow: showArrow, className: className, children: [showArrow && jsxRuntime.jsx(react.Tooltip.Arrow, {}), content] })] }));
17351
+ return (jsxRuntime.jsxs(react.Tooltip, { isOpen: isOpen, defaultOpen: defaultOpen, onOpenChange: onOpenChange, isDisabled: isDisabled, delay: delay, closeDelay: closeDelay, trigger: trigger, children: [jsxRuntime.jsx(react.Tooltip.Trigger, { render: renderTooltipTrigger(children) }), jsxRuntime.jsxs(react.Tooltip.Content, { placement: placement, offset: offset, showArrow: showArrow, className: contentClassName, children: [arrow, content] })] }));
15061
17352
  }
15062
17353
  // Táctil → controlado, el tap sobre el trigger alterna la visibilidad.
15063
17354
  // El placement cambia a `touchPlacement` (arriba por defecto) para que no
@@ -15069,7 +17360,7 @@ delay = 0, closeDelay, trigger, className, children, }) => {
15069
17360
  return (jsxRuntime.jsxs(react.Tooltip, { isOpen: open, onOpenChange: setOpen, isDisabled: isDisabled, children: [jsxRuntime.jsx(react.Tooltip.Trigger, { children: jsxRuntime.jsx("span", { ref: triggerRef, className: "inline-flex", onPointerUp: (event) => {
15070
17361
  event.stopPropagation();
15071
17362
  setOpen(!open);
15072
- }, children: children }) }), jsxRuntime.jsxs(react.Tooltip.Content, { placement: touchPlacement, offset: offset, showArrow: showArrow, className: className, children: [showArrow && jsxRuntime.jsx(react.Tooltip.Arrow, {}), content] })] }));
17363
+ }, children: children }) }), jsxRuntime.jsxs(react.Tooltip.Content, { placement: touchPlacement, offset: offset, showArrow: showArrow, className: contentClassName, children: [arrow, content] })] }));
15073
17364
  };
15074
17365
 
15075
17366
  /**
@@ -15836,26 +18127,6 @@ Object.defineProperty(exports, "CloseButton", {
15836
18127
  enumerable: true,
15837
18128
  get: function () { return react.CloseButton; }
15838
18129
  });
15839
- Object.defineProperty(exports, "Drawer", {
15840
- enumerable: true,
15841
- get: function () { return react.Drawer; }
15842
- });
15843
- Object.defineProperty(exports, "DrawerBody", {
15844
- enumerable: true,
15845
- get: function () { return react.DrawerBody; }
15846
- });
15847
- Object.defineProperty(exports, "DrawerContent", {
15848
- enumerable: true,
15849
- get: function () { return react.DrawerContent; }
15850
- });
15851
- Object.defineProperty(exports, "DrawerFooter", {
15852
- enumerable: true,
15853
- get: function () { return react.DrawerFooter; }
15854
- });
15855
- Object.defineProperty(exports, "DrawerHeader", {
15856
- enumerable: true,
15857
- get: function () { return react.DrawerHeader; }
15858
- });
15859
18130
  Object.defineProperty(exports, "DropdownItem", {
15860
18131
  enumerable: true,
15861
18132
  get: function () { return react.DropdownItem; }
@@ -15968,7 +18239,12 @@ exports.DateRangePicker = DateRangePicker;
15968
18239
  exports.DateSelector = DateSelector;
15969
18240
  exports.Divider = Divider;
15970
18241
  exports.DonutChart = DonutChart;
18242
+ exports.Drawer = Drawer;
18243
+ exports.DrawerBody = DrawerBody;
18244
+ exports.DrawerContent = DrawerContent;
15971
18245
  exports.DrawerFilters = DrawerFilters;
18246
+ exports.DrawerFooter = DrawerFooter;
18247
+ exports.DrawerHeader = DrawerHeader;
15972
18248
  exports.Dropdown = Dropdown;
15973
18249
  exports.DropdownSection = DropdownSection;
15974
18250
  exports.EmailPreview = EmailPreview;
@@ -16034,7 +18310,7 @@ exports.ThemeContext = ThemeContext;
16034
18310
  exports.ThemePicker = ThemePicker;
16035
18311
  exports.ThemeProvider = ThemeProvider;
16036
18312
  exports.TimeInput = TimeInput;
16037
- exports.TimeInputComponent = TimeInput;
18313
+ exports.TimeInputComponent = TimeInputComponent;
16038
18314
  exports.ToastContext = ToastContext;
16039
18315
  exports.Tooltip = Tooltip;
16040
18316
  exports.TwoColumnLayoutAgent = TwoColumnLayoutAgent;