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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/dist/assets/css/styles.css +23 -1
  2. package/dist/index.cjs.js +2661 -369
  3. package/dist/index.esm.js +2692 -400
  4. package/dist/types/compat/heroui-v2-shims.d.ts +143 -30
  5. package/dist/types/compat/heroui-v2-shims.d.ts.map +1 -1
  6. package/dist/types/components/autocomplete/AutoComplete.d.ts.map +1 -1
  7. package/dist/types/components/autocomplete/AutoComplete.types.d.ts +75 -3
  8. package/dist/types/components/autocomplete/AutoComplete.types.d.ts.map +1 -1
  9. package/dist/types/components/autocomplete/index.d.ts +1 -1
  10. package/dist/types/components/autocomplete/index.d.ts.map +1 -1
  11. package/dist/types/components/button/Button.d.ts +3 -0
  12. package/dist/types/components/button/Button.d.ts.map +1 -1
  13. package/dist/types/components/button/Button.types.d.ts +24 -1
  14. package/dist/types/components/button/Button.types.d.ts.map +1 -1
  15. package/dist/types/components/button/_internal/press-event.d.ts +16 -0
  16. package/dist/types/components/button/_internal/press-event.d.ts.map +1 -0
  17. package/dist/types/components/button/index.d.ts +1 -1
  18. package/dist/types/components/button/index.d.ts.map +1 -1
  19. package/dist/types/components/card/Card.d.ts +14 -1
  20. package/dist/types/components/card/Card.d.ts.map +1 -1
  21. package/dist/types/components/card/Card.types.d.ts +54 -1
  22. package/dist/types/components/card/Card.types.d.ts.map +1 -1
  23. package/dist/types/components/card/index.d.ts +1 -1
  24. package/dist/types/components/card/index.d.ts.map +1 -1
  25. package/dist/types/components/chip/Chip.d.ts +1 -1
  26. package/dist/types/components/chip/Chip.d.ts.map +1 -1
  27. package/dist/types/components/chip/Chip.types.d.ts +33 -7
  28. package/dist/types/components/chip/Chip.types.d.ts.map +1 -1
  29. package/dist/types/components/datepicker/DatePicker.d.ts +1 -1
  30. package/dist/types/components/datepicker/DatePicker.d.ts.map +1 -1
  31. package/dist/types/components/datepicker/DatePicker.types.d.ts +15 -0
  32. package/dist/types/components/datepicker/DatePicker.types.d.ts.map +1 -1
  33. package/dist/types/components/datepicker/index.d.ts +1 -1
  34. package/dist/types/components/datepicker/index.d.ts.map +1 -1
  35. package/dist/types/components/drawer-filters/DrawerFilters.d.ts.map +1 -1
  36. package/dist/types/components/drawer-filters/DrawerFilters.types.d.ts +2 -0
  37. package/dist/types/components/drawer-filters/DrawerFilters.types.d.ts.map +1 -1
  38. package/dist/types/components/drawer-filters/_internal/DrawerFiltersHeader.d.ts.map +1 -1
  39. package/dist/types/components/dropdown/Dropdown.d.ts +4 -2
  40. package/dist/types/components/dropdown/Dropdown.d.ts.map +1 -1
  41. package/dist/types/components/dropdown/Dropdown.types.d.ts +61 -5
  42. package/dist/types/components/dropdown/Dropdown.types.d.ts.map +1 -1
  43. package/dist/types/components/dropdown/_internal/renderTrigger.d.ts +47 -0
  44. package/dist/types/components/dropdown/_internal/renderTrigger.d.ts.map +1 -0
  45. package/dist/types/components/dropdown/index.d.ts +1 -1
  46. package/dist/types/components/dropdown/index.d.ts.map +1 -1
  47. package/dist/types/components/icon/Icon.d.ts.map +1 -1
  48. package/dist/types/components/icon/Icon.types.d.ts +9 -1
  49. package/dist/types/components/icon/Icon.types.d.ts.map +1 -1
  50. package/dist/types/components/input/Input.d.ts +4 -0
  51. package/dist/types/components/input/Input.d.ts.map +1 -1
  52. package/dist/types/components/input/Input.types.d.ts +37 -1
  53. package/dist/types/components/input/Input.types.d.ts.map +1 -1
  54. package/dist/types/components/modal/Modal.d.ts +3 -2
  55. package/dist/types/components/modal/Modal.d.ts.map +1 -1
  56. package/dist/types/components/modal/Modal.types.d.ts +51 -3
  57. package/dist/types/components/modal/Modal.types.d.ts.map +1 -1
  58. package/dist/types/components/modal/ModalFacadeContext.d.ts +27 -0
  59. package/dist/types/components/modal/ModalFacadeContext.d.ts.map +1 -0
  60. package/dist/types/components/modal/_internal/ModalBody.d.ts +1 -1
  61. package/dist/types/components/modal/_internal/ModalBody.d.ts.map +1 -1
  62. package/dist/types/components/modal/_internal/ModalContent.d.ts +10 -5
  63. package/dist/types/components/modal/_internal/ModalContent.d.ts.map +1 -1
  64. package/dist/types/components/modal/_internal/ModalFooter.d.ts +1 -1
  65. package/dist/types/components/modal/_internal/ModalFooter.d.ts.map +1 -1
  66. package/dist/types/components/modal/_internal/ModalHeader.d.ts +4 -2
  67. package/dist/types/components/modal/_internal/ModalHeader.d.ts.map +1 -1
  68. package/dist/types/components/pagination/Pagination.d.ts +10 -0
  69. package/dist/types/components/pagination/Pagination.d.ts.map +1 -1
  70. package/dist/types/components/pagination/Pagination.types.d.ts +21 -0
  71. package/dist/types/components/pagination/Pagination.types.d.ts.map +1 -1
  72. package/dist/types/components/select/Select.d.ts.map +1 -1
  73. package/dist/types/components/select/Select.types.d.ts +172 -5
  74. package/dist/types/components/select/Select.types.d.ts.map +1 -1
  75. package/dist/types/components/select/_internal/v2-collection.d.ts +64 -0
  76. package/dist/types/components/select/_internal/v2-collection.d.ts.map +1 -0
  77. package/dist/types/components/select/index.d.ts +1 -1
  78. package/dist/types/components/select/index.d.ts.map +1 -1
  79. package/dist/types/components/table/AuraTable.types.d.ts +5 -3
  80. package/dist/types/components/table/AuraTable.types.d.ts.map +1 -1
  81. package/dist/types/components/table/Table.d.ts +15 -5
  82. package/dist/types/components/table/Table.d.ts.map +1 -1
  83. package/dist/types/components/table/TableContent.d.ts +32 -0
  84. package/dist/types/components/table/TableContent.d.ts.map +1 -0
  85. package/dist/types/components/table/_internal/v2-compat.d.ts +94 -0
  86. package/dist/types/components/table/_internal/v2-compat.d.ts.map +1 -0
  87. package/dist/types/components/tabs/Tabs.d.ts.map +1 -1
  88. package/dist/types/components/tabs/Tabs.types.d.ts +3 -2
  89. package/dist/types/components/tabs/Tabs.types.d.ts.map +1 -1
  90. package/dist/types/components/textarea/Textarea.d.ts.map +1 -1
  91. package/dist/types/components/textarea/Textarea.types.d.ts +37 -0
  92. package/dist/types/components/textarea/Textarea.types.d.ts.map +1 -1
  93. package/dist/types/components/textarea/useTextareaAutosize.d.ts +13 -0
  94. package/dist/types/components/textarea/useTextareaAutosize.d.ts.map +1 -0
  95. package/dist/types/components/time-input/TimeInput.d.ts.map +1 -1
  96. package/dist/types/components/time-input/TimeInput.types.d.ts +13 -1
  97. package/dist/types/components/time-input/TimeInput.types.d.ts.map +1 -1
  98. package/dist/types/components/tooltip/Tooltip.d.ts.map +1 -1
  99. package/dist/types/components/tooltip/Tooltip.types.d.ts +31 -1
  100. package/dist/types/components/tooltip/Tooltip.types.d.ts.map +1 -1
  101. package/dist/types/components/tooltip/_internal/renderTrigger.d.ts +3 -0
  102. package/dist/types/components/tooltip/_internal/renderTrigger.d.ts.map +1 -0
  103. package/dist/types/components/tooltip/index.d.ts +1 -1
  104. package/dist/types/components/tooltip/index.d.ts.map +1 -1
  105. package/dist/types/index.d.ts +6 -6
  106. package/dist/types/index.d.ts.map +1 -1
  107. package/package.json +1 -1
package/dist/index.cjs.js CHANGED
@@ -19,27 +19,104 @@ 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;
32
+ /**
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
+ });
43
120
  /**
44
121
  * `HeroUIProvider` ya no existe en v3 (los componentes no necesitan Provider para pintarse). Pero
45
122
  * el de v2 hacía dos cosas que react-aria sí necesita y que las apps no pueden montar por su
@@ -59,15 +136,42 @@ function HeroUIProvider({ children, locale, navigate, useHref, }) {
59
136
  }
60
137
  return jsxRuntime.jsx(jsxRuntime.Fragment, { children: contents });
61
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
+ };
62
153
  /**
63
154
  * `Image` se elimina en v3 (usar `<img>` nativo, mismo criterio aplicado en
64
- * `SocialMediaPreview`/`WhatsAppPreview`). Shim: mismo nombre, props de un `<img>` nativo — NO
65
- * reproduce `radius`/`isBlurred`/`isZoomed`/`fallbackSrc` de v2 (sin equivalente razonable sin
66
- * 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).
67
170
  */
68
- function Image$1(props) {
171
+ function Image$1({ radius = "lg", classNames, removeWrapper: _removeWrapper, className, ...props }) {
172
+ return (
69
173
  // biome-ignore lint/a11y/useAltText: passthrough genérico, `alt` lo exige ComponentProps<'img'>
70
- return jsxRuntime.jsx("img", { ...props });
174
+ jsxRuntime.jsx("img", { ...props, className: react.cn(IMAGE_RADIUS_CLASS[radius], className, classNames?.img) }));
71
175
  }
72
176
  // ---------------------------------------------------------------------------
73
177
  // Hooks
@@ -92,6 +196,42 @@ function useDisclosure(props) {
92
196
  };
93
197
  }
94
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
+
95
235
  /** `clamp(0.01 * size, 0.2, size > 100 ? 0.75 : 0.5)` de v2, en ms. */
96
236
  function duracionDeOnda(size) {
97
237
  const maximo = size > 100 ? 750 : 500;
@@ -384,6 +524,77 @@ const SHAPE_CLASS = {
384
524
  outline: "border-2",
385
525
  ghost: "",
386
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
+ }
387
598
  /**
388
599
  * Botón genérico basado en HeroUI v3 siguiendo las reglas de diseño BeweOS (TECH-2459 · F3.0).
389
600
  *
@@ -399,8 +610,15 @@ const SHAPE_CLASS = {
399
610
  * - `disableRipple`: v3 no tiene ripple — reimplementado en `_internal/ripple.tsx`, así que la
400
611
  * prop vuelve a apagar algo.
401
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.
402
616
  */
403
- 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";
404
622
  const shape = SHAPE_BY_VARIANT$1[variant];
405
623
  const v3Variant = BASE_V3_VARIANT[shape];
406
624
  const spinnerNode = spinner ?? jsxRuntime.jsx(react.Spinner, { size: "sm", color: "current" });
@@ -438,20 +656,62 @@ const Button = ({ color = "primary", size = "md", variant = "solid", radius = "s
438
656
  * `.linda-aura > *` ya los alcanza.
439
657
  */
440
658
  const contenido = ai ? (jsxRuntime.jsx("span", { className: "linda-aura-content", children: children })) : (children);
441
- return (jsxRuntime.jsxs(react.Button, { ...rest, variant: v3Variant, size: size, isIconOnly: isIconOnly, isPending: isLoading, isDisabled: isDisabled || isLoading, onPress: alPulsar, className: [
442
- SHAPE_CLASS[shape],
443
- RADIUS_CLASS$a[radius],
444
- ai ? CLASE_AURA[ai] : "",
445
- // P-05: v2 apagaba transición + hundido con esta prop; v3 no tiene la prop, así
446
- // que el interruptor es una clase propia (`Button.css`).
447
- disableAnimation ? "button--no-animation" : "",
448
- // P-06b: hover atenuado de v2 en vez del cambio de fondo de v3 (ver
449
- // `HOVER_ATENUADO` arriba y `neutralize-v3.css`).
450
- HOVER_ATENUADO[shape] ? "button--hover-dim" : "",
451
- className,
452
- ]
453
- .filter(Boolean)
454
- .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 }));
455
715
  };
456
716
 
457
717
  // TECH-2459 · F3.0 — Matriz de traducción v2 → v3 (mismo mecanismo que Button, documentado en
@@ -497,6 +757,56 @@ const SHAPE_BY_VARIANT = {
497
757
  flat: "soft",
498
758
  bordered: "outline",
499
759
  faded: "outline",
760
+ // `shadow` de v2 es `solid` + sombra teñida (ver `SHADOW_CLASS`).
761
+ shadow: "solid",
762
+ // `dot` de v2: chip neutro con un punto de color (ver `DOT_BASE_CLASS`).
763
+ dot: "dot",
764
+ };
765
+ // ── `variant="dot"` (TECH-2459, reportado por beweossmbs sobre la .140) ─────────────────────
766
+ //
767
+ // Literal de `@heroui/theme@2.4.15` (`chip.js`) y `@heroui/chip@2.2.25` (`chip.js`):
768
+ //
769
+ // variant.dot.base = "border-medium border-default text-foreground bg-transparent"
770
+ // slots.dot = "w-2 h-2 ml-1 rounded-full" + color.<c>.dot = "bg-<c>" (default: "bg-default-400")
771
+ // render = `isDot && !startContent` → `<span class={slots.dot}/>` en el hueco de `startContent`
772
+ //
773
+ // O sea: el `color` NO tiñe el chip (no hay ningún `compoundVariant` de `dot` × color), solo el
774
+ // punto; el chip queda neutro con el borde de 2px de `default`. Y el punto NO cuenta como
775
+ // `hasStartContent` (`!!avatar || !!startContent`), así que el texto conserva su sangrado izquierdo
776
+ // normal. El compound `{ isOneChar, variant: "dot" }` (base "w-auto h-7 px-1", content "px-2")
777
+ // anula el cuadrado de un carácter: aquí basta con no aplicarlo.
778
+ //
779
+ // Uso real: `shared/ui/components/page-header` de beweossmbs reenvía un `variant` de su propio tipo
780
+ // que incluye `"dot"` (hoy ningún sitio lo pasa, pero el tipo no compilaba).
781
+ const DOT_BASE_CLASS = "border-2 border-default bg-transparent text-foreground";
782
+ const DOT_SLOT_CLASS = "h-2 w-2 ml-1 shrink-0 rounded-full";
783
+ // Clases literales por color (Tailwind 4 solo genera lo que encuentra escrito). Los 5 colores fijos
784
+ // de Aurora no existían en v2: su punto toma el tono del texto que ya les da `CUSTOM_COLOR_CLASSES`.
785
+ const DOT_COLOR_CLASS = {
786
+ default: "bg-default-400",
787
+ primary: "bg-primary",
788
+ secondary: "bg-secondary",
789
+ success: "bg-success",
790
+ warning: "bg-warning",
791
+ danger: "bg-danger",
792
+ red: "bg-red-500",
793
+ purple: "bg-purple-500",
794
+ blue: "bg-blue-500",
795
+ green: "bg-green-500",
796
+ orange: "bg-orange-500",
797
+ };
798
+ // `variant="shadow"` de v2, literal de `@heroui/theme@2.4.15` (`dist/utils/variants.js`,
799
+ // `colorVariants.shadow`): `shadow-lg shadow-<color>/40 bg-<color> text-<color>-foreground`
800
+ // (`default` usa `/50`). El relleno y el texto son los de `solid`; aquí solo se añade la sombra.
801
+ // Clases literales (no plantillas) para que Tailwind las genere al escanear `src/`. Los 5 colores
802
+ // fijos de Aurora (red/purple/…) no existían en v2 con `shadow`: reciben la sombra neutra.
803
+ const SHADOW_CLASS$1 = {
804
+ default: "shadow-lg shadow-default/50",
805
+ primary: "shadow-lg shadow-primary/40",
806
+ secondary: "shadow-lg shadow-secondary/40",
807
+ success: "shadow-lg shadow-success/40",
808
+ warning: "shadow-lg shadow-warning/40",
809
+ danger: "shadow-lg shadow-danger/40",
500
810
  };
501
811
  // TECH-2459 · F5 · P-08c — Tamaños de v2 (`chip.ts`): la altura era FIJA y el texto más grande que
502
812
  // en v3, y el sangrado horizontal salía de dos slots (`base` + `content`):
@@ -601,6 +911,7 @@ const V3_VARIANT_BY_SHAPE = {
601
911
  soft: "soft",
602
912
  transparent: "tertiary",
603
913
  outline: "tertiary",
914
+ dot: "tertiary",
604
915
  };
605
916
  /**
606
917
  * TECH-2459 · F5 · P-08d — La tabla `flat` de v2, medida contra los PNG congelados y no deducida.
@@ -708,6 +1019,13 @@ const CLOSE_ICON_SIZE_CLASS = {
708
1019
  lg: "size-5",
709
1020
  };
710
1021
  function colorStyle(color, shape) {
1022
+ if (shape === "dot") {
1023
+ // `dot`: el chip es neutro para cualquier color (el color va solo en el punto).
1024
+ return {
1025
+ "--chip-bg": "transparent",
1026
+ "--chip-fg": "var(--color-foreground)",
1027
+ };
1028
+ }
711
1029
  const t = COLOR_TOKENS[color];
712
1030
  if (shape === "solid") {
713
1031
  return { "--chip-bg": t.bg, "--chip-fg": t.fg };
@@ -734,7 +1052,7 @@ function colorStyle(color, shape) {
734
1052
  * y el hueco que queda tras el botón de cierre sean distintos, como en v2 (ver `SIZE_CLASS` y
735
1053
  * `CONTENT_PADDING`).
736
1054
  */
737
- const Chip = ({ color = "default", variant = "solid", size = "md", radius = "full", startContent, endContent, onClose, isDisabled = false, className, classNames, children, id, }) => {
1055
+ const Chip = ({ color = "default", variant = "solid", size = "md", radius = "full", startContent, endContent, onClose, isDisabled = false, className, classNames, children, as, style: consumerStyle, ...domProps }) => {
738
1056
  const isStandard = STANDARD_COLORS.has(color);
739
1057
  const shape = SHAPE_BY_VARIANT[variant];
740
1058
  // Literal de v2 (`@heroui/chip@2.2.18`, `use-chip.js:63`):
@@ -743,14 +1061,17 @@ const Chip = ({ color = "default", variant = "solid", size = "md", radius = "ful
743
1061
  const hasEndContent = Boolean(endContent) || Boolean(onClose);
744
1062
  // `isOneChar` de v2 (`use-chip.js:58`): SOLO si el children es un string de un carácter — un
745
1063
  // número no cuenta. El cuadrado además exige no llevar nada a los lados (ver `ONE_CHAR_CLASS`).
746
- const isSquare = typeof children === "string" &&
1064
+ const isDot = shape === "dot";
1065
+ const isSquare = !isDot &&
1066
+ typeof children === "string" &&
747
1067
  children.length === 1 &&
748
1068
  !startContent &&
749
1069
  !hasEndContent;
750
1070
  const vars = isStandard
751
1071
  ? colorStyle(color, shape)
752
1072
  : undefined;
753
- const customClass = !isStandard ? CUSTOM_COLOR_CLASSES[color] : "";
1073
+ // Con `dot` el color no tiñe el chip, tampoco el de los colores fijos.
1074
+ const customClass = isStandard || isDot ? "" : CUSTOM_COLOR_CLASSES[color];
754
1075
  const outlineClass = shape === "outline" ? "border-2" : "";
755
1076
  // El `dark:text-*` que v2 declaraba solo para el texto de `flat` en success/warning/danger
756
1077
  // (P-08d). Va como clase porque `--chip-fg` es inline y no puede depender del tema; la capa
@@ -758,12 +1079,27 @@ const Chip = ({ color = "default", variant = "solid", size = "md", radius = "ful
758
1079
  const flatDarkFgClass = isStandard && shape === "soft"
759
1080
  ? (COLOR_TOKENS[color].flatFgDarkClass ?? "")
760
1081
  : "";
761
- const style = vars
1082
+ const baseStyle = vars
762
1083
  ? shape === "outline"
763
1084
  ? { ...vars, borderColor: vars["--chip-fg"] }
764
1085
  : vars
765
1086
  : undefined;
766
- return (jsxRuntime.jsxs(react.Chip, { id: id, size: size, variant: V3_VARIANT_BY_SHAPE[shape],
1087
+ // El `style` del consumidor va DESPUÉS: pisa el relleno/texto calculados (uso real en
1088
+ // beweossmbs: `style={{ backgroundColor, color }}` con el color del agente, 5 sitios).
1089
+ const style = baseStyle || consumerStyle ? { ...baseStyle, ...consumerStyle } : undefined;
1090
+ const shadowClass = variant === "shadow"
1091
+ ? isStandard
1092
+ ? SHADOW_CLASS$1[color]
1093
+ : "shadow-lg"
1094
+ : "";
1095
+ // `as` de v2 (`Component = as || "div"`). v3 no tiene `as`, pero su raíz acepta `render`, que
1096
+ // recibe las props DOM ya compuestas (clases, `data-slot`, `ref`…) y devuelve el elemento.
1097
+ const render = as
1098
+ ? (props) => $8WNJc$react.createElement(as, props)
1099
+ : undefined;
1100
+ return (jsxRuntime.jsxs(react.Chip
1101
+ // `aria-disabled` ANTES del spread: si el consumidor manda el suyo, gana el suyo.
1102
+ , { "aria-disabled": isDisabled || undefined, ...domProps, render: render, size: size, variant: V3_VARIANT_BY_SHAPE[shape],
767
1103
  // `cn` (tailwind-merge) y no una concatenación de texto: el consumidor tiene que poder
768
1104
  // pisar tamaño y radio (`className="h-5 text-[10px]"` es un uso real en beweossmbs).
769
1105
  // Con una cadena suelta ambas clases llegan al DOM y gana la que Tailwind emita más
@@ -772,18 +1108,20 @@ const Chip = ({ color = "default", variant = "solid", size = "md", radius = "ful
772
1108
  className: react.cn(SIZE_CLASS$6[size],
773
1109
  // Después de `SIZE_CLASS` a propósito: el cuadrado de un carácter ENCOGE la caja
774
1110
  // respecto al tamaño normal y tiene que ganar en tailwind-merge.
775
- 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
1111
+ isSquare ? ONE_CHAR_CLASS[size] : "", RADIUS_CLASS$9[radius], outlineClass, isDot ? DOT_BASE_CLASS : "", shadowClass, flatDarkFgClass, customClass, isDisabled ? "opacity-50 pointer-events-none" : "", classNames?.base, className), style: style, children: [startContent, isDot && !startContent && (jsxRuntime.jsx("span", { "aria-hidden": "true", "data-slot": "dot", className: react.cn(DOT_SLOT_CLASS, DOT_COLOR_CLASS[color]) })), jsxRuntime.jsx("span", { className: react.cn("flex-1 text-inherit font-normal", startContent
776
1112
  ? CONTENT_PADDING[size].leftWithContent
777
1113
  : CONTENT_PADDING[size].left, hasEndContent
778
1114
  ? CONTENT_PADDING[size].rightWithContent
779
1115
  : CONTENT_PADDING[size].right,
780
1116
  // `content: "px-0 flex-none"` del compound de un carácter. Va después de los
781
1117
  // `pl-*`/`pr-*` para que tailwind-merge los borre (un `px` posterior sí gana).
782
- isSquare ? "px-0 flex-none" : "", classNames?.label), children: children }), endContent, onClose && (jsxRuntime.jsx(react.CloseButton, { "aria-label": "Cerrar",
1118
+ isSquare ? "px-0 flex-none" : "", classNames?.label,
1119
+ // `content` es el nombre v2 del slot (16 usos en beweossmbs); va el último.
1120
+ classNames?.content), children: children }), endContent, onClose && (jsxRuntime.jsx(react.CloseButton, { "aria-label": "Cerrar",
783
1121
  // `.close-button` de v3 impone caja de 24px, `bg-default` y `text-muted`; v2 no
784
1122
  // pintaba fondo ni caja: el glifo ERA el botón. Se anulan con utilidades (capa
785
1123
  // `utilities` gana a `components`, incluido el `& svg { size-4 -mx-0.5 my-0.5 }`).
786
- 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" }) }) }))] }));
1124
+ 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" }) }) }))] }));
787
1125
  };
788
1126
 
789
1127
  const sizeMap = {
@@ -795,7 +1133,11 @@ const sizeMap = {
795
1133
  const IconComponent = ({ icon, size = "md", className, style, color, hFlip, vFlip, flip, rotate, ...nativeProps }) => {
796
1134
  // Un número son píxeles tal cual (p. ej. 64): la escala con nombre se queda en 32 y
797
1135
  // `width`/`height` no son props del componente (caerían en el <span>, sin efecto).
798
- const iconSize = typeof size === "number" ? `${size}px` : (sizeMap[size] ?? sizeMap.md);
1136
+ // Un nombre fuera de la escala (`"xs"`, el alias v2 del tipo) cae al default, como en v2
1137
+ // (`sizeMap[size] ?? sizeMap.md`, `@beweco/aurora-ui@0.6.85`): 20px.
1138
+ const iconSize = typeof size === "number"
1139
+ ? `${size}px`
1140
+ : (sizeMap[size] ?? sizeMap.md);
799
1141
  return (jsxRuntime.jsx("span", { ...nativeProps, children: jsxRuntime.jsx(react$1.Icon, { icon: icon, width: iconSize, height: iconSize, className: className, style: style, color: color, hFlip: hFlip, vFlip: vFlip, flip: flip, rotate: rotate }) }));
800
1142
  };
801
1143
 
@@ -824,10 +1166,10 @@ const IconComponent = ({ icon, size = "md", className, style, color, hFlip, vFli
824
1166
  // (`bg-default-200` / `bg-default-300` en variant `flat`) se reponen también por clase por el
825
1167
  // mismo motivo.
826
1168
  //
827
- // El RADIO no entra aquí a propósito: se compone por pieza más abajo. Estas clases se concatenan
828
- // como texto (no con `cn`/tailwind-merge), así que dos radios en la misma cadena no se resuelven
829
- // por orden de escritura sino por el orden en que Tailwind los emite — `rounded-none` ganaba a
830
- // `rounded-[12px]` y la celda activa salía a escuadra.
1169
+ // El RADIO no entra aquí a propósito: se compone por pieza más abajo (`cellRadius`). Las piezas se
1170
+ // fusionan con `cn` (tailwind-merge), que resuelve dos clases del mismo grupo por su POSICIÓN en
1171
+ // la cadena. Antes se concatenaban como texto y decidía el orden en que Tailwind las emite:
1172
+ // `rounded-none` ganaba a `rounded-[12px]` y la celda activa salía a escuadra.
831
1173
  // ⚠️ TECH-2459 · F5 · R2 — `shadow-xs`, no `shadow-sm`: **Tailwind 4 corrió la escala de sombras
832
1174
  // un paso**. El `shadow-sm` de Tailwind 3 (que es el que emitía v2) es el `shadow-xs` de Tailwind
833
1175
  // 4. Literal de `node_modules/tailwindcss/theme.css:406-407`:
@@ -852,11 +1194,38 @@ const CELL_CLASS = "size-9 md:size-9 min-w-9 bg-default-100 text-sm text-default
852
1194
  * `w-fit` reproduce el `max-w-fit` del wrapper de v2.
853
1195
  */
854
1196
  const STRIP_CLASS = "w-fit gap-0 rounded-[12px] shadow-xs";
855
- /** Celdas interiores: a escuadra (el `[&:not(:first-of-type):not(:last-of-type)]:rounded-none` de v2). */
856
- const INNER_RADIUS_CLASS = "rounded-none";
857
- /** Extremos de la tira: solo el lado exterior lleva radio, como el `isCompact` de v2. */
858
- const NAV_START_CLASS = `${CELL_CLASS} w-9 px-0 rounded-s-[12px] rounded-e-none`;
859
- const NAV_END_CLASS = `${CELL_CLASS} w-9 px-0 rounded-e-[12px] rounded-s-none`;
1197
+ /**
1198
+ * `isCompact={false}`: celdas sueltas. Literales del `tv` de v2 sin el bloque `isCompact`: el
1199
+ * `wrapper` vuelve a `gap-1` (4px) sin sombra común, y cada celda lleva su propio radio
1200
+ * (`radius: md` → `rounded-medium`, 12px).
1201
+ */
1202
+ const STRIP_LOOSE_CLASS = "w-fit gap-1";
1203
+ /** Controles anterior/siguiente: celda cuadrada, sin el padding lateral de v3. */
1204
+ const NAV_CLASS = "w-9 px-0";
1205
+ /**
1206
+ * Radio de cada pieza según su posición en la tira.
1207
+ *
1208
+ * Compacta (el `isCompact` de v2): celdas interiores a escuadra
1209
+ * (`[&:not(:first-of-type):not(:last-of-type)]:rounded-none`) y solo el lado EXTERIOR de los
1210
+ * extremos redondeado (`first-of-type:rounded-e-none` / `last-of-type:rounded-s-none`). Los
1211
+ * extremos son las flechas cuando hay `showControls`, y las propias páginas cuando no: por eso
1212
+ * se decide por posición y no por tipo de pieza.
1213
+ */
1214
+ function cellRadius(isCompact, position) {
1215
+ if (!isCompact) {
1216
+ return "rounded-[12px]";
1217
+ }
1218
+ if (position.isFirst && position.isLast) {
1219
+ return "rounded-[12px]";
1220
+ }
1221
+ if (position.isFirst) {
1222
+ return "rounded-s-[12px] rounded-e-none";
1223
+ }
1224
+ if (position.isLast) {
1225
+ return "rounded-e-[12px] rounded-s-none";
1226
+ }
1227
+ return "rounded-none";
1228
+ }
860
1229
  /**
861
1230
  * Página activa. v2 la pintaba con un "cursor" absoluto de `bg-primary text-primary-foreground` y
862
1231
  * `rounded-medium` por encima de la celda; aquí se pinta la celda misma con los mismos tokens.
@@ -864,7 +1233,39 @@ const NAV_END_CLASS = `${CELL_CLASS} w-9 px-0 rounded-e-[12px] rounded-s-none`;
864
1233
  * Sin esto v3 la pinta con `--pagination-link-bg: var(--default)`, el gris CRUDO de v3 y no el
865
1234
  * `--color-default` de Aurora (falso amigo, decisiones-aceptadas-f1.md D-F1-3).
866
1235
  */
867
- const ACTIVE_LINK_CLASS = "rounded-[12px] bg-primary text-primary-foreground hover:bg-primary active:bg-primary";
1236
+ const ACTIVE_LINK_CLASS = "rounded-[12px]";
1237
+ /**
1238
+ * `color` de v2 → la página activa. Literal de `colorVariants.solid` (`@heroui/theme@2.4.15`,
1239
+ * `dist/components/pagination.js`): `bg-<color> text-<color>-foreground`. Los estados hover y
1240
+ * pulsado se fijan al mismo fondo porque la celda base los cambia (`CELL_CLASS`) y en v2 el
1241
+ * cursor activo no reaccionaba al ratón. Cadenas literales completas: Tailwind solo emite lo que
1242
+ * encuentra escrito entero.
1243
+ *
1244
+ * `default` es el gris de Aurora (`--color-default`), no el `--default` crudo de v3 (falso
1245
+ * amigo, decisiones-aceptadas-f1.md D-F1-3).
1246
+ */
1247
+ const ACTIVE_COLOR_CLASS = {
1248
+ default: "bg-default text-default-foreground hover:bg-default active:bg-default",
1249
+ primary: "bg-primary text-primary-foreground hover:bg-primary active:bg-primary",
1250
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary active:bg-secondary",
1251
+ success: "bg-success text-success-foreground hover:bg-success active:bg-success",
1252
+ warning: "bg-warning text-warning-foreground hover:bg-warning active:bg-warning",
1253
+ danger: "bg-danger text-danger-foreground hover:bg-danger active:bg-danger",
1254
+ };
1255
+ /**
1256
+ * `showShadow` de v2 → sombra de color bajo la página activa. Literal del compound
1257
+ * `{ showShadow: true, color }` de v2: `cursor: [colorVariants.shadow[color], "shadow-md"]`, que
1258
+ * tras tailwind-merge queda `shadow-md shadow-<color>/40` (`/50` en `default`). `shadow-md` no
1259
+ * cambió de valor entre Tailwind 3 y 4 (el corrimiento de la escala solo afectó a `sm`/`DEFAULT`).
1260
+ */
1261
+ const ACTIVE_SHADOW_CLASS = {
1262
+ default: "shadow-md shadow-default/50",
1263
+ primary: "shadow-md shadow-primary/40",
1264
+ secondary: "shadow-md shadow-secondary/40",
1265
+ success: "shadow-md shadow-success/40",
1266
+ warning: "shadow-md shadow-warning/40",
1267
+ danger: "shadow-md shadow-danger/40",
1268
+ };
868
1269
  /**
869
1270
  * Traducciones por defecto en español. Todas son `aria-label`: este componente no tiene más
870
1271
  * texto visible que los números de página.
@@ -976,12 +1377,22 @@ function getPaginationRange(total, current) {
976
1377
  * `aria-label` los dos botones llegaban vacíos al árbol de accesibilidad.
977
1378
  *
978
1379
 
1380
+ * API v2 que las apps siguen escribiendo (medido en beweossmbs `origin/staging`: 17 usos, todos
1381
+ * con `showControls`, 14 con `isCompact`, 12 con `color="primary"`, 11 con `showShadow`):
1382
+ * - `showControls` (default `true`): pinta anterior/siguiente; con `false` desaparecen.
1383
+ * - `isCompact` (default `true`): tira compacta; con `false`, celdas sueltas (`gap-1`).
1384
+ * - `color` (default `"primary"`): color de la página activa.
1385
+ * - `showShadow` (default `false`): sombra de color bajo la página activa.
1386
+ * Los defaults `true` de las dos primeras son los que forzaba la fachada v2 de Aurora
1387
+ * (`<HeroUIPagination showControls isCompact {...props} />`), así que quien no las pasa se ve
1388
+ * exactamente igual que hasta ahora.
1389
+ *
979
1390
  * ⚠️ `size` se acepta pero no se aplica: mismo bug preexistente que v2 (destructurado, nunca
980
1391
  * reenviado a HeroUI) — fuera de scope de esta migración, reportado aparte (ver
981
1392
  * findings/decisiones-aceptadas-g2.md y f5-L7-sueltos.md · P-17). v3 SÍ tiene la prop (emite
982
1393
  * `.pagination--sm|--md|--lg`), así que arreglarlo es un cambio de tamaño visible: ticket aparte.
983
1394
  */
984
- const Pagination = ({ total, page, initialPage = 1, onChange, size: _size = "sm", className, isDisabled, translations = {}, }) => {
1395
+ const Pagination = ({ total, page, initialPage = 1, onChange, size: _size = "sm", className, isDisabled, showControls = true, isCompact = true, showShadow = false, color = "primary", translations = {}, }) => {
985
1396
  const [internalPage, setInternalPage] = $8WNJc$react.useState(initialPage);
986
1397
  const currentPage = page ?? internalPage;
987
1398
  const t = { ...DEFAULT_TRANSLATIONS$g, ...translations };
@@ -998,18 +1409,26 @@ const Pagination = ({ total, page, initialPage = 1, onChange, size: _size = "sm"
998
1409
  // `dist/pagination.js:464`). Si `page` viene fuera de rango, `indexOf` da -1 y todas las
999
1410
  // elipsis avanzan — también igual que v2, que no protegía ese caso.
1000
1411
  const activeIndex = items.indexOf(currentPage);
1412
+ // Posición de cada pieza en la tira, para el radio compacto: con controles, los extremos
1413
+ // son las flechas y ninguna página toca el borde.
1414
+ const lastItemIndex = items.length - 1;
1415
+ const itemPosition = (index) => ({
1416
+ isFirst: !showControls && index === 0,
1417
+ isLast: !showControls && index === lastItemIndex,
1418
+ });
1419
+ const activeClass = react.cn(ACTIVE_COLOR_CLASS[color], showShadow ? ACTIVE_SHADOW_CLASS[color] : "", ACTIVE_LINK_CLASS);
1001
1420
  return (
1002
1421
  // `w-fit` reproduce el `max-w-fit` del wrapper de v2: `.pagination` de v3 es `w-full` +
1003
1422
  // `justify-between`, así que dentro de un `flex justify-center` (como el de AccordionList) la
1004
1423
  // tira se quedaba pegada a la izquierda en vez de centrarse.
1005
- 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
1424
+ 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
1006
1425
  // v3 mete el galón en un `<span aria-hidden="true">`
1007
1426
  // (`pagination.js:165`), así que sin esto el botón se queda SIN nombre
1008
1427
  // accesible: un lector de pantalla lo anuncia como "botón" a secas. v2 sí
1009
1428
  // lo nombraba ("previous page button", `use-pagination.js:213`), pero en
1010
1429
  // inglés y hardcodeado; aquí va por `translations` (regla obligatoria de
1011
1430
  // i18n del repo, skill `auraui-i18n`).
1012
- , { "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) => {
1431
+ , { "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) => {
1013
1432
  if (item === "ellipsis") {
1014
1433
  const isBefore = index < activeIndex;
1015
1434
  const label = isBefore
@@ -1017,14 +1436,14 @@ const Pagination = ({ total, page, initialPage = 1, onChange, size: _size = "sm"
1017
1436
  : t.jumpForwardAriaLabel;
1018
1437
  return (
1019
1438
  // biome-ignore lint/suspicious/noArrayIndexKey: solo hay como máximo 2 elipsis, posición estable
1020
- 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
1439
+ 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
1021
1440
  ? currentPage - DOTS_JUMP
1022
1441
  : currentPage + DOTS_JUMP), children: [jsxRuntime.jsx(EllipsisDotsIcon, {}), jsxRuntime.jsx(JumpIcon, { isBefore: isBefore })] }) }, `ellipsis-${index}`));
1023
1442
  }
1024
- 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));
1025
- }), jsxRuntime.jsx(react.Pagination.Item, { children: jsxRuntime.jsx(react.Pagination.Next
1443
+ 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));
1444
+ }), showControls && (jsxRuntime.jsx(react.Pagination.Item, { children: jsxRuntime.jsx(react.Pagination.Next
1026
1445
  /* Mismo motivo que en `Previous`: el galón es `aria-hidden`. */
1027
- , { "aria-label": t.nextPageAriaLabel, className: NAV_END_CLASS, isDisabled: isDisabled || currentPage >= total, onPress: () => goTo(currentPage + 1), children: jsxRuntime.jsx(react.Pagination.NextIcon, {}) }) })] }) }));
1446
+ , { "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, {}) }) }))] }) }));
1028
1447
  };
1029
1448
 
1030
1449
  // TECH-2459 · F5 · P-01 — Valores tomados del tema v2 REAL (`@heroui/theme@2.4.15`,
@@ -1268,6 +1687,44 @@ const VARIANT_CLASS$5 = {
1268
1687
  * En reposo no cambia nada (el anillo de v3 ya vale 0 ahí), así que la referencia v2 se mantiene.
1269
1688
  */
1270
1689
  const FIELD_FOCUS_CLASS$5 = "ring-0 ring-offset-0 outline-0";
1690
+ /*
1691
+ * TECH-2459 · hallazgo beweossmbs 23-sep — `labelPlacement` de v2, repuesta. Misma solución que
1692
+ * `TimeInput` en la .139: una tabla de clases por colocación. La barra de la agenda
1693
+ * (`agenda-toolbar.component.tsx`) pide `"outside-left"` y v1 no tenía la prop: la etiqueta caía
1694
+ * encima y la barra crecía una línea.
1695
+ *
1696
+ * `outside` es EXACTAMENTE lo que la fachada pintaba hasta ahora (la raíz `flex flex-col gap-1`,
1697
+ * sin clases de colocación en el grupo ni en la ayuda), así que quien no pasa la prop no cambia un
1698
+ * píxel. `outside-left` son los literales del `tv` de v2 (`@heroui/theme@2.4.15` →
1699
+ * `dist/components/date-input.js`, `labelPlacement["outside-left"]`, que es el `tv` del que tira el
1700
+ * `DatePicker` de v2):
1701
+ *
1702
+ * base → "flex-row items-center gap-x-2 flex-nowrap"
1703
+ * + `data-[has-helper=true]:pb-[calc(theme(fontSize.tiny)_+_8px)]`
1704
+ * inputWrapper → "relative flex-1"
1705
+ * helperWrapper→ "absolute top-[calc(100%_+_2px)] start-0"
1706
+ *
1707
+ * La ayuda y el error no pueden ser un tercer hijo de la fila (quedarían a la derecha del campo):
1708
+ * v2 los sacaba del flujo bajo el campo y reservaba su alto con el `pb` de la raíz. Aquí igual;
1709
+ * `theme(fontSize.tiny)` de v2 (12px) es `--text-xs` en Tailwind 4, y la raíz lleva `relative`
1710
+ * para que el `absolute` se ancle a ella.
1711
+ */
1712
+ const ROOT_PLACEMENT_CLASS = {
1713
+ outside: "flex flex-col gap-1",
1714
+ "outside-left": "relative flex flex-row items-center gap-x-2 flex-nowrap",
1715
+ };
1716
+ const ROOT_HELPER_SPACE_CLASS = {
1717
+ outside: undefined,
1718
+ "outside-left": "pb-[calc(var(--text-xs)+8px)]",
1719
+ };
1720
+ const GROUP_PLACEMENT_CLASS = {
1721
+ outside: undefined,
1722
+ "outside-left": "relative flex-1",
1723
+ };
1724
+ const HELPER_PLACEMENT_CLASS = {
1725
+ outside: undefined,
1726
+ "outside-left": "absolute top-[calc(100%+2px)] start-0",
1727
+ };
1271
1728
  // TECH-2459 · F5 · P-10 — El "círculo" del calendario. v2 (`@heroui/theme@2.4.15`,
1272
1729
  // `calendar.ts`) solo tenía UN círculo y significaba "día seleccionado":
1273
1730
  // cellButton: "w-8 h-8 … rounded-full" + color primary: "data-[selected=true]:bg-primary
@@ -1294,7 +1751,7 @@ const CALENDAR_CELL_CLASS$1 = "size-8 place-self-center rounded-full " +
1294
1751
  * (grid) para que la API pública siga siendo un único componente con props planas, igual que en
1295
1752
  * v2 (label, description, errorMessage, endContent, variant, size, radius).
1296
1753
  */
1297
- function DatePicker({ 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 }) {
1754
+ 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 }) {
1298
1755
  const generatedId = $8WNJc$react.useId();
1299
1756
  const autoId = id || generatedId;
1300
1757
  const calendarLabel = (typeof label === "string" ? label : undefined) ??
@@ -1302,17 +1759,24 @@ function DatePicker({ label, description, errorMessage, id, size = "md", variant
1302
1759
  "Calendario";
1303
1760
  const calendarBottomContent = calendarBottomContentProp ?? CalendarBottomContent;
1304
1761
  const calendarTopContent = calendarTopContentProp ?? CalendarTopContent;
1762
+ const hasHelper = Boolean((description && !isInvalid) || (isInvalid && errorMessage));
1305
1763
  const { className: calendarClassName, style: calendarStyle, ...restCalendarProps } = calendarProps ?? {};
1306
- return (jsxRuntime.jsxs(react.DatePicker, { ...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([
1764
+ return (jsxRuntime.jsxs(react.DatePicker, { ...props, id: autoId, "aria-label": ariaLabel, isInvalid: isInvalid, isRequired: isRequired, isDisabled: isDisabled, className: withConsumerClass([
1765
+ ROOT_PLACEMENT_CLASS[labelPlacement],
1766
+ fullWidth && "w-full",
1767
+ hasHelper && ROOT_HELPER_SPACE_CLASS[labelPlacement],
1768
+ classNames?.base,
1769
+ ], 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([
1307
1770
  SIZE_CLASS$5[size],
1308
1771
  RADIUS_CLASS$8[radius],
1309
1772
  VARIANT_CLASS$5[variant],
1310
1773
  FIELD_FOCUS_CLASS$5,
1311
- ], 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: classNames?.description, 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([
1774
+ GROUP_PLACEMENT_CLASS[labelPlacement],
1775
+ ], 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([
1312
1776
  typeof calendarClassName === "string"
1313
1777
  ? calendarClassName
1314
1778
  : undefined,
1315
- ], 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: classNames?.errorMessage, children: errorMessage }))] }));
1779
+ ], 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 }))] }));
1316
1780
  }
1317
1781
 
1318
1782
  // Mismo mecanismo que DatePicker.tsx / Input.tsx / Select.tsx (findings/
@@ -1723,6 +2187,10 @@ const INVALID_TEXT_CLASS$2 = "text-danger";
1723
2187
  * - Radius configurable (v3 no tiene la prop).
1724
2188
  * - `startContent`/`endContent` → `InputGroup.Prefix`/`InputGroup.Suffix`.
1725
2189
  * - `isClearable` → botón de limpiar propio (`CloseButton`); solo en modo controlado (`value`).
2190
+ * - `onClear` (API v2) → basta para mostrar la X; se llama al limpiar (X o Escape) y el foco
2191
+ * vuelve al campo, como en v2.
2192
+ * - `classNames`: los 10 slots de v2, cada uno en su nodo equivalente (tabla en `Input.types.ts`).
2193
+ * - `labelPlacement` (API v2): aceptado y sin efecto — el label siempre va fuera.
1726
2194
  *
1727
2195
  * `ref` apunta al `<input>` REAL (`HTMLInputElement`), no al `InputGroup` ni al `TextField` que lo
1728
2196
  * envuelven: el consumidor que necesita el nodo —insertar texto en la posición del cursor con
@@ -1742,7 +2210,10 @@ const Input = $8WNJc$react.forwardRef(({ label, description, errorMessage, id,
1742
2210
  // `lg` que había aquí venía del `argTypes.defaultValue.summary` de la story, que YA estaba mal
1743
2211
  // en v2. Con "lg" todos los campos de las 4 apps que no pasan `size` salían 8px más altos, con
1744
2212
  // 2px más de padding lateral y el texto a 16px en vez de 14.
1745
- size = "md", variant = "bordered", radius = "md", placeholder, startContent, endContent, isClearable = false, isInvalid = false, isRequired = false, isDisabled = false, isReadOnly = false,
2213
+ size = "md", variant = "bordered", radius = "md", placeholder, startContent, endContent, isClearable: isClearableProp = false, onClear,
2214
+ // Sin efecto (ver `InputProps.labelPlacement`): se saca del spread para que no llegue
2215
+ // al `<input>` como atributo desconocido.
2216
+ labelPlacement: _labelPlacement, isInvalid = false, isRequired = false, isDisabled = false, isReadOnly = false,
1746
2217
  // `fullWidth` por defecto a `true`: HeroUI v2 lo traía en sus `defaultVariants`
1747
2218
  // (verificado en el paquete v2 compilado, `dist/components/input.js`:
1748
2219
  // `defaultVariants: { variant:"flat", color:"default", size:"md", fullWidth:true, … }`, con
@@ -1750,7 +2221,21 @@ size = "md", variant = "bordered", radius = "md", placeholder, startContent, end
1750
2221
  // `undefined` y el campo dejaba de llenar su contenedor: en una fila flex se encogía al ancho
1751
2222
  // de su contenido (medido por S2: 208px dentro de una fila de 592, 336px de hueco). En una
1752
2223
  // columna no se notaba porque el `stretch` del flex lo tapaba — pero afecta a cualquier app.
1753
- fullWidth = true, value, defaultValue, onChange, onValueChange, className, classNames, required, name, type = "text", ...rest }, ref) => {
2224
+ fullWidth = true, value, defaultValue, onChange, onValueChange, className, classNames, required, name, type = "text", onKeyDown, ...rest }, ref) => {
2225
+ // El `<input>` real, también para la fachada: v2 devolvía el foco al campo al limpiar
2226
+ // (`domRef.current?.focus()` en `handleClear`). Se combina con el `ref` del consumidor.
2227
+ const inputRef = $8WNJc$react.useRef(null);
2228
+ const setInputRef = $8WNJc$react.useCallback((node) => {
2229
+ inputRef.current = node;
2230
+ if (typeof ref === "function") {
2231
+ ref(node);
2232
+ }
2233
+ else if (ref) {
2234
+ ref.current = node;
2235
+ }
2236
+ }, [ref]);
2237
+ // v2: `isClearable = !!onClear || originalProps.isClearable` (`use-input.ts`).
2238
+ const isClearable = isClearableProp || Boolean(onClear);
1754
2239
  const generatedId = $8WNJc$react.useId();
1755
2240
  const inputId = id || generatedId;
1756
2241
  // ⚠️ `isRequired` (prop de HeroUI, asterisco visual) y `required` (atributo HTML nativo, solo
@@ -1758,7 +2243,24 @@ fullWidth = true, value, defaultValue, onChange, onValueChange, className, class
1758
2243
  // Detectado por VRT (`FormExample`, que solo pasa `required`): tratarlos como lo mismo aquí
1759
2244
  // introducía un asterisco que v2 no tenía. `isRequired` maneja el asterisco (vía TextField);
1760
2245
  // `required` se reenvía tal cual al `<input>` nativo, sin efecto visual.
1761
- const showClear = isClearable && !isDisabled && !isReadOnly && Boolean(value);
2246
+ // ── El valor lo guarda el `TextField`, no el `<input>` ───────────────────────────────────
2247
+ //
2248
+ // En react-aria el dueño del valor es el `TextField`: `useTextField` lo guarda con
2249
+ // `useControlledState(value, defaultValue ?? "")` y se lo inyecta al `<input>` como `value`
2250
+ // por contexto (`react-aria-components/dist/private/TextField.mjs`). Antes la fachada pasaba
2251
+ // `value`/`defaultValue` al `InputGroup.Input` y ninguno al `TextField`, así que en modo NO
2252
+ // controlado el `TextField` arrancaba en "" y el `<input>` recibía a la vez `value=""` (del
2253
+ // contexto) y `defaultValue`: medido en jsdom, `<Input defaultValue="hola" />` salía VACÍO
2254
+ // ya en el primer render, con el aviso de React «both value and defaultValue». Mismo arreglo
2255
+ // que en `Textarea.tsx`.
2256
+ //
2257
+ // Aquí además la fachada lleva el valor no controlado en su propio estado (en vez de
2258
+ // dejárselo al `TextField`), porque limpiar tiene que poder vaciarlo: `isClearable`/`onClear`
2259
+ // en modo no controlado no tenían manera de escribir en el estado interno del `TextField`.
2260
+ const isControlled = value !== undefined;
2261
+ const [uncontrolledValue, setUncontrolledValue] = $8WNJc$react.useState(defaultValue ?? "");
2262
+ const currentValue = isControlled ? value : uncontrolledValue;
2263
+ const showClear = isClearable && !isDisabled && !isReadOnly && Boolean(currentValue);
1762
2264
  // Solo flota cuando hay label y NO hay placeholder: con placeholder, v2 ya mostraba el label
1763
2265
  // arriba desde el primer render (ver el bloque de arriba).
1764
2266
  const labelFlotante = Boolean(label) && !placeholder;
@@ -1766,7 +2268,12 @@ fullWidth = true, value, defaultValue, onChange, onValueChange, className, class
1766
2268
  onChange?.(event);
1767
2269
  onValueChange?.(event.target.value);
1768
2270
  };
1769
- const handleClear = () => {
2271
+ // Orden de v2 (`handleClear` de `use-input.ts`): primero el valor a "" —que en v2 disparaba
2272
+ // `onValueChange`—, luego `onClear`, y el foco vuelve al campo.
2273
+ const clearValue = () => {
2274
+ if (!isControlled) {
2275
+ setUncontrolledValue("");
2276
+ }
1770
2277
  onValueChange?.("");
1771
2278
  if (onChange) {
1772
2279
  const fakeEvent = {
@@ -1775,15 +2282,38 @@ fullWidth = true, value, defaultValue, onChange, onValueChange, className, class
1775
2282
  };
1776
2283
  onChange(fakeEvent);
1777
2284
  }
2285
+ onClear?.();
1778
2286
  };
1779
- 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" : "",
2287
+ const handleClear = () => {
2288
+ clearValue();
2289
+ inputRef.current?.focus();
2290
+ };
2291
+ // Escape limpia, igual que v2 (`handleKeyDown` de `use-input.ts`: con valor, limpiable y
2292
+ // no de solo lectura). El `onKeyDown` del consumidor se llama primero.
2293
+ const handleKeyDown = (event) => {
2294
+ onKeyDown?.(event);
2295
+ if (event.key === "Escape" && showClear) {
2296
+ clearValue();
2297
+ }
2298
+ };
2299
+ return (jsxRuntime.jsxs(react.TextField, { id: inputId, value: currentValue,
2300
+ // Solo el modo no controlado escribe aquí; en controlado manda el `value` del padre
2301
+ // (que se entera por `onChange`/`onValueChange` del `<input>`).
2302
+ 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" : "",
2303
+ // `mainWrapper` de v2 no tiene nodo propio aquí (ver tabla en `Input.types.ts`):
2304
+ // se fusiona en la raíz antes que `base`, que es el slot más externo.
2305
+ classNames?.mainWrapper,
2306
+ // `base` de v2 = la raíz. v2 los juntaba así: `clsx(classNames.base, className)`.
2307
+ classNames?.base,
1780
2308
  // El `className` del consumidor, en la raíz y en último lugar: el slot `base` de
1781
2309
  // v2 y la posición que le deja ganar en tailwind-merge (ver bloque de arriba).
1782
2310
  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,
1783
2311
  // Incondicional, como el `inputWrapper` de v2: la caja del campo llena
1784
2312
  // siempre su raíz, y es la raíz la que responde a `fullWidth` y al ancho que
1785
2313
  // pase el consumidor.
1786
- "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 }))] }));
2314
+ "w-full",
2315
+ // v3 fusiona `innerWrapper` e `inputWrapper` de v2 en este único elemento.
2316
+ 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 }))] }));
1787
2317
  });
1788
2318
  Input.displayName = "Input";
1789
2319
 
@@ -1890,12 +2420,70 @@ const RADIUS_CLASS$5 = {
1890
2420
  // v3 no tiene prop `shadow`: su Card siempre usa `shadow-surface`. Los valores de v2 se
1891
2421
  // reproducen con los tokens propios `--aurora-shadow-*` (emitidos en F1, mismo valor exacto que
1892
2422
  // generaba el plugin `heroui()` de v2 — ver findings/tabla-mapeo-tokens.md §2).
2423
+ //
2424
+ // ⚠️ El prefijo `shadow:` dentro del corchete NO es decorativo. Sin él, tailwind-merge 3 clasifica
2425
+ // `shadow-[var(--x)]` como COLOR de sombra (grupo `shadow-color`), no como sombra: medido con el
2426
+ // `cn` real, `cn("shadow-[var(--aurora-shadow-small)]", "shadow-2xl")` conserva LAS DOS, y en el
2427
+ // CSS de Tailwind 4 la arbitraria se emite después de `shadow-2xl` (orden de candidato), así que
2428
+ // ganaba la nuestra — el `shadow-2xl` que pide `whatsapp-setup-modal` no se veía. Con la etiqueta
2429
+ // `shadow:` tailwind-merge la reconoce como sombra y la retira cuando la app pasa la suya; el CSS
2430
+ // que emite Tailwind 4 es idéntico (`--tw-shadow: var(--aurora-shadow-small)`, comprobado con
2431
+ // `@tailwindcss/node`).
1893
2432
  const SHADOW_CLASS = {
1894
2433
  none: "shadow-none",
1895
- sm: "shadow-[var(--aurora-shadow-small)]",
1896
- md: "shadow-[var(--aurora-shadow-medium)]",
1897
- lg: "shadow-[var(--aurora-shadow-large)]",
1898
- };
2434
+ sm: "shadow-[shadow:var(--aurora-shadow-small)]",
2435
+ md: "shadow-[shadow:var(--aurora-shadow-medium)]",
2436
+ lg: "shadow-[shadow:var(--aurora-shadow-large)]",
2437
+ };
2438
+ // Literales del `tv` de v2 (`@heroui/theme@2.4.15`, `dist/components/card.js`), con sus tokens
2439
+ // traducidos a los que existen en Tailwind 4 + Aurora:
2440
+ // isPressable → `cursor-pointer` + compound `data-[pressed=true]:scale-[0.97]
2441
+ // tap-highlight-transparent` (el hundido lo hace aquí `:active`, que es lo que
2442
+ // marcaba `data-pressed` en un botón nativo)
2443
+ // disableAnimation=false → `transition-transform-background motion-reduce:transition-none`
2444
+ // foco → `dataFocusVisibleClasses` (`outline-2 outline-focus outline-offset-2`)
2445
+ // isHoverable → `data-[hover=true]:bg-content2` (aquí `hover:`, react-aria ya no escribe
2446
+ // `data-hover` — trampa 3 de la skill `auraui-components`)
2447
+ // isDisabled → `opacity-disabled cursor-not-allowed` (`opacity-disabled` era 0.5 en v2)
2448
+ // fullWidth → `w-full`
2449
+ 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]";
2450
+ const PRESS_ANIMATION_CLASS = "transition-[transform,background-color] motion-reduce:transition-none active:scale-[0.97]";
2451
+ const HOVERABLE_CLASS = "hover:bg-content2";
2452
+ const DISABLED_CLASS = "opacity-50 cursor-not-allowed";
2453
+ // Controles que tienen su propia pulsación. Un clic que nace dentro de uno de ellos es SUYO, no de
2454
+ // la card: los `Button` de Aurora (react-aria) ya paran la propagación del `click` (medido en
2455
+ // jsdom con el `Button` de la librería), pero un `<button>`/`<a>` nativo o un `<input>` no.
2456
+ const CONTROL_INTERNO = 'button, a[href], input, select, textarea, summary, [role="button"], [role="link"], [role="checkbox"], [role="switch"], [role="menuitem"], [role="tab"], [role="option"]';
2457
+ function desdeControlInterno(event) {
2458
+ const target = event.target;
2459
+ if (!target || target === event.currentTarget) {
2460
+ return false;
2461
+ }
2462
+ const control = target.closest?.(CONTROL_INTERNO);
2463
+ return (!!control &&
2464
+ control !== event.currentTarget &&
2465
+ event.currentTarget.contains(control));
2466
+ }
2467
+ /**
2468
+ * Tecla → `PressEvent` con la forma del de react-aria (`pointerType: "keyboard"`, punto en el
2469
+ * centro de la card, como hace `pressEventFromClick` con un click sintético de `detail === 0`).
2470
+ */
2471
+ function pressEventFromKey(event) {
2472
+ const target = event.currentTarget;
2473
+ const rect = target.getBoundingClientRect();
2474
+ return {
2475
+ type: "press",
2476
+ pointerType: "keyboard",
2477
+ target,
2478
+ shiftKey: event.shiftKey,
2479
+ ctrlKey: event.ctrlKey,
2480
+ metaKey: event.metaKey,
2481
+ altKey: event.altKey,
2482
+ x: rect.width / 2,
2483
+ y: rect.height / 2,
2484
+ continuePropagation: () => undefined,
2485
+ };
2486
+ }
1899
2487
  /**
1900
2488
  * Card genérica siguiendo las reglas de diseño BeweOS basada en HeroUI v3.
1901
2489
  *
@@ -1909,18 +2497,94 @@ const SHADOW_CLASS = {
1909
2497
  * - Shadow: none, sm, md, lg (configurable, reimplementado vía className — v3 no tiene la prop)
1910
2498
  * - Radius: sm (por defecto, configurable, reimplementado vía className — v3 no tiene la prop)
1911
2499
  * - Padding configurable (sm, md, lg)
2500
+ * - `className` de la app AL FINAL y fusionado con `cn` (tailwind-merge), como el `slots.base({
2501
+ * class })` de v2: un `p-0`/`rounded-xl`/`shadow-2xl` de la app retira el default de la fachada
2502
+ * en vez de competir con él por orden de CSS.
2503
+ * - `ref` y TODAS las props DOM (`style`, `onMouse*`, `aria-*`, `data-*`…) llegan al nodo raíz,
2504
+ * como en v2 (`HTMLHeroUIProps<"div">`).
2505
+ * - API v2 de card pulsable: `isPressable`/`onPress`/`disableAnimation`/`disableRipple`, más
2506
+ * `isHoverable`/`isDisabled`/`fullWidth`.
2507
+ *
2508
+ * `ref` se reenvía con `forwardRef` (patrón de las fachadas de campo del repo, ver
2509
+ * `Input.tsx`) y apunta al nodo raíz, que es siempre un `<div>` (con `isPressable`,
2510
+ * `<div role="button">`).
1912
2511
  */
1913
- const Card = ({ children, className = "", shadow = "sm", radius = "sm", padding = "md", ...props }) => {
1914
- const combinedClassName = [
1915
- paddingClasses[padding],
1916
- RADIUS_CLASS$5[radius],
1917
- SHADOW_CLASS[shadow],
1918
- className,
1919
- ]
1920
- .filter(Boolean)
1921
- .join(" ");
1922
- return (jsxRuntime.jsx(react.Card, { className: combinedClassName, ...props, children: children }));
1923
- };
2512
+ const Card = $8WNJc$react.forwardRef(({ children, className, shadow = "sm", radius = "sm", padding = "md", isPressable = false, onPress, onClick, onKeyDown, onKeyUp, disableAnimation = false, disableRipple = false, isHoverable = false, isDisabled = false, fullWidth = false, render, ...props }, ref) => {
2513
+ // Como en v2 (`@heroui/card@2.2.21`, `useCard`): `disableAnimation` apaga también la onda.
2514
+ const sinRipple = disableRipple || disableAnimation;
2515
+ const { ondas, lanzar, quitar } = useRipple();
2516
+ const pulsable = isPressable && !isDisabled;
2517
+ const press = $8WNJc$react.useCallback((pressEvent) => {
2518
+ onPress?.(pressEvent);
2519
+ if (!sinRipple) {
2520
+ lanzar(pressEvent);
2521
+ }
2522
+ }, [onPress, sinRipple, lanzar]);
2523
+ const handleClick = $8WNJc$react.useCallback((event) => {
2524
+ // Card pulsable deshabilitada: ni `onClick` de la app, como el `disabled` nativo del
2525
+ // `<button>` que era hasta la .140.
2526
+ if (isPressable && isDisabled) {
2527
+ return;
2528
+ }
2529
+ onClick?.(event);
2530
+ if (!pulsable || desdeControlInterno(event)) {
2531
+ return;
2532
+ }
2533
+ press(pressEventFromClick(event));
2534
+ }, [onClick, isPressable, isDisabled, pulsable, press]);
2535
+ // Teclado de un `<button>` nativo, que el `<div role="button">` no trae: Enter pulsa al
2536
+ // bajar la tecla y Espacio al soltarla. `preventDefault` en ambos: Espacio haría scroll de
2537
+ // la página y Enter podría enviar un `<form>` que envuelva la card. Solo cuando el foco está
2538
+ // EN la card: una tecla sobre un botón interno es de ese botón (react-aria ya para su
2539
+ // propagación, y esta guarda cubre también a los controles nativos).
2540
+ const handleKeyDown = $8WNJc$react.useCallback((event) => {
2541
+ onKeyDown?.(event);
2542
+ if (!pulsable || event.target !== event.currentTarget) {
2543
+ return;
2544
+ }
2545
+ if (event.key === " ") {
2546
+ event.preventDefault();
2547
+ }
2548
+ else if (event.key === "Enter") {
2549
+ event.preventDefault();
2550
+ if (!event.repeat) {
2551
+ press(pressEventFromKey(event));
2552
+ }
2553
+ }
2554
+ }, [onKeyDown, pulsable, press]);
2555
+ const handleKeyUp = $8WNJc$react.useCallback((event) => {
2556
+ onKeyUp?.(event);
2557
+ if (!pulsable ||
2558
+ event.target !== event.currentTarget ||
2559
+ event.key !== " ") {
2560
+ return;
2561
+ }
2562
+ event.preventDefault();
2563
+ press(pressEventFromKey(event));
2564
+ }, [onKeyUp, pulsable, press]);
2565
+ 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 : "",
2566
+ // El `className` de la app, último: gana por fusión (ver JSDoc).
2567
+ className);
2568
+ // v2 cambiaba el ELEMENTO a `<button>` con `isPressable`, y hasta la .140 esta fachada lo
2569
+ // imitaba con el `render` de v3. Se quitó porque las apps meten botones DENTRO de la card
2570
+ // pulsable (`ChannelCard` de smbs, Linda → Compartir: «Compartir»/«Integrar»), y
2571
+ // `<button>` dentro de `<button>` es HTML inválido: React avisa en consola
2572
+ // («cannot be a descendant of <button>», medido en jsdom) y el parser del navegador lo
2573
+ // reparenta. La card se queda en el `<div>` de v3 con la semántica de botón de v2
2574
+ // (`useCard`: `role: "button"` + `tabIndex: isPressable ? 0 : -1`) y el teclado a mano
2575
+ // (ver `handleKeyDown`). El `render` de la app pasa tal cual.
2576
+ const pressableProps = isPressable
2577
+ ? {
2578
+ role: "button",
2579
+ tabIndex: isDisabled ? undefined : 0,
2580
+ "aria-disabled": isDisabled || undefined,
2581
+ onKeyDown: handleKeyDown,
2582
+ onKeyUp: handleKeyUp,
2583
+ }
2584
+ : {};
2585
+ return (jsxRuntime.jsxs(react.Card, { ...props, ...pressableProps, ref: ref, className: combinedClassName, "data-pressable": isPressable || undefined, "data-disabled": isDisabled || undefined, onClick: isPressable || onClick ? handleClick : undefined, render: render, children: [children, isPressable && ondas.length > 0 && (jsxRuntime.jsx(CapaRipple, { ondas: ondas, onFin: quitar }))] }));
2586
+ });
2587
+ Card.displayName = "Card";
1924
2588
 
1925
2589
  const H2 = ({ children, className, ...props }) => {
1926
2590
  return (jsxRuntime.jsx("h2", { className: react.cn("text-lg font-semibold", className), ...props, children: children }));
@@ -2158,10 +2822,221 @@ function withCollectionItemIds(children) {
2158
2822
  return Object.keys(patch).length > 0 ? $8WNJc$react.cloneElement(element, patch) : element;
2159
2823
  }
2160
2824
 
2825
+ /**
2826
+ * Repone en los items y secciones de una colección de v3 la API de CONTENIDO de v2 que las apps
2827
+ * siguen escribiendo en `SelectItem`/`AutocompleteItem`/`SelectSection` (TECH-2459, hallazgo
2828
+ * beweossmbs 23-sep).
2829
+ *
2830
+ * QUÉ HACÍA v2 (`@heroui/listbox@2.3.31`, `dist/chunk-2TBDFBA3.mjs`, leído del tarball):
2831
+ *
2832
+ * <li …getItemProps()>
2833
+ * {startContent && <span data-slot="startContent">…}
2834
+ * {description
2835
+ * ? <div wrapper><span title>{rendered}</span><span description>{description}</span></div>
2836
+ * : <span title>{rendered}</span>}
2837
+ * {selectedIcon}
2838
+ * {endContent && <span data-slot="endContent">…}
2839
+ * </li>
2840
+ *
2841
+ * con los slots del `tv` (`@heroui/theme@2.4.15`, `dist/components/listbox.js:100-139`):
2842
+ * `wrapper: "w-full flex flex-col items-start justify-center"`, `title: "flex-1 text-small
2843
+ * font-normal"`, `description: "w-full text-tiny text-foreground-500 …"`. La sección
2844
+ * (`chunk-Q2EVG7SO.mjs`) pintaba la cabecera `heading: "pl-1 text-tiny text-foreground-500"` y,
2845
+ * con `showDivider` (default `false`), un `Divider` con `mt-2` al final.
2846
+ *
2847
+ * QUÉ HACE v3. `ListBox.Item` no tiene NINGUNA de esas props (la guía oficial: «Place icons
2848
+ * manually in item content», «Use `Description` component»; y `title` de la sección → `Header`).
2849
+ * Pasadas tal cual se descartan en silencio: el `filterDOMProps` de react-aria no las deja llegar
2850
+ * al DOM y el item pinta solo su texto.
2851
+ *
2852
+ * POR QUÉ AQUÍ Y NO EN EL SHIM. `SelectItem`/`AutocompleteItem`/`SelectSection` tienen que seguir
2853
+ * siendo el MISMO objeto que `ListBoxItem`/`ListBoxSection`: el normalizador de ids
2854
+ * (`utils/collection-items.ts`) y el indicador (`with-item-indicator.tsx`) reconocen los items por
2855
+ * identidad de `element.type`, y un envoltorio los volvería invisibles (los items dentro de una
2856
+ * sección perderían su `id` derivado de la `key`, que es el defecto nº 1 de la migración). Así que
2857
+ * el shim solo TIPA las props v2 y esta función, llamada desde la fachada que tiene los items en
2858
+ * la mano, las traduce a la anatomía de v3.
2859
+ *
2860
+ * NO-OP cuando no hay nada que traducir: un item sin `description`/`startContent`/`endContent`/
2861
+ * `classNames` se devuelve tal cual (mismo objeto), así que los 150+ items planos de las apps no
2862
+ * cambian ni un nodo.
2863
+ */
2864
+ /** Marca de lo que v2 NO pintaba en el disparador: allí solo iba el `textValue` del item. */
2865
+ const ITEM_EXTRA_ATTR = "data-aura-item-extra";
2866
+ /**
2867
+ * Clase para `Select.Value`: esconde en el disparador lo que v2 solo pintaba en la lista.
2868
+ *
2869
+ * `SelectValue` de react-aria repinta los CHILDREN del item elegido dentro del disparador
2870
+ * (`react-aria-components/dist/private/Select.mjs:226-262`). v2 pintaba ahí solo el `textValue`
2871
+ * (`@heroui/select@2.4.33`, `dist/chunk-BJWIL32D.mjs`: `state.selectedItems.map(i =>
2872
+ * i.textValue).join(", ")`), así que el icono lateral no debe colarse junto al valor. La
2873
+ * descripción se esconde SOLA: `Description` de v3 devuelve `null` sin el slot de texto
2874
+ * `description`, y `SelectValue` resetea ese contexto (`TextContext.Provider value={undefined}`,
2875
+ * misma línea) — el mismo mecanismo que usa v3 para esconder su indicador ahí (`select.css:121`).
2876
+ */
2877
+ const HIDE_ITEM_EXTRAS_CLASS = `[&_[${ITEM_EXTRA_ATTR}]]:hidden`;
2878
+ // Literales de v2 portados a la pieza de v3, sin clases v2 muertas (`text-small`/`text-tiny` no
2879
+ // existen sin el plugin `heroui()` y `cn` las borraría). El tamaño del título NO se fija: el
2880
+ // desplegable de v3 ya es `text-sm` (= el `text-small` de v2) y dentro del disparador tiene que
2881
+ // heredar el tamaño del campo (`lg` = 16px).
2882
+ const ITEM_WRAPPER_CLASS = "flex w-full min-w-0 flex-col items-start justify-center";
2883
+ const ITEM_TITLE_CLASS$1 = "min-w-0 flex-1 font-normal";
2884
+ // `Description` de v3 ya trae `text-xs text-muted` (`description.css`), y `text-muted` es el mismo
2885
+ // zinc-500/400 que el `text-foreground-500` de v2 (medido en `Dropdown.tsx`, bloque de la cabecera).
2886
+ const ITEM_DESCRIPTION_CLASS = "w-full";
2887
+ const ITEM_SIDE_CLASS = "flex shrink-0 items-center";
2888
+ // Cabecera de sección: mismo único ajuste que la fachada `Dropdown` (ver `SECTION_HEADING_CLASS`
2889
+ // en `Dropdown.tsx`: tamaño y color de `.header` ya coinciden con v2; el peso no).
2890
+ const SECTION_HEADING_CLASS$1 = "font-normal";
2891
+ // `mt-2` del slot `divider` de v2; el ancho y el color los pone el `Separator` de v3.
2892
+ const SECTION_DIVIDER_CLASS = "mt-2";
2893
+ /** `isEmpty` de v2 (`@heroui/shared-utils`): null/undefined/""/false no cuentan. */
2894
+ function hasValue$2(node) {
2895
+ return node !== null && node !== undefined && node !== "" && node !== false;
2896
+ }
2897
+ function translateItem(element) {
2898
+ const { children, className, description, startContent, endContent, classNames, } = element.props;
2899
+ const hasDescription = hasValue$2(description);
2900
+ const hasStart = hasValue$2(startContent);
2901
+ const hasEnd = hasValue$2(endContent);
2902
+ if (!(hasDescription || hasStart || hasEnd || classNames)) {
2903
+ return element;
2904
+ }
2905
+ // Solo `classNames.base` (el caso de `timezone-search-select`): nada que recomponer, basta
2906
+ // fusionar la clase en la raíz. Y children como función (render props de react-aria): no se
2907
+ // puede componer alrededor sin romper la firma, así que se aplican solo las clases de la raíz.
2908
+ const onlyBase = !(hasDescription ||
2909
+ hasStart ||
2910
+ hasEnd ||
2911
+ classNames?.title ||
2912
+ classNames?.wrapper ||
2913
+ classNames?.description);
2914
+ if (onlyBase || typeof children === "function") {
2915
+ return $8WNJc$react.cloneElement(element, {
2916
+ className: react.cn(classNames?.base, className) || undefined,
2917
+ description: undefined,
2918
+ startContent: undefined,
2919
+ endContent: undefined,
2920
+ classNames: undefined,
2921
+ });
2922
+ }
2923
+ const title = (jsxRuntime.jsx("span", { className: react.cn(ITEM_TITLE_CLASS$1, classNames?.title), children: children }));
2924
+ 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 }))] }));
2925
+ return $8WNJc$react.cloneElement(element, {
2926
+ // v2: `cn(classNames?.base, className)` (`use-listbox-item.mjs`), así que `className` gana.
2927
+ className: react.cn(classNames?.base, className) || undefined,
2928
+ description: undefined,
2929
+ startContent: undefined,
2930
+ endContent: undefined,
2931
+ classNames: undefined,
2932
+ children: next,
2933
+ });
2934
+ }
2935
+ function translateSection(element) {
2936
+ const { children, className, title, showDivider, classNames } = element.props;
2937
+ const inner = withV2ItemContent(children);
2938
+ const hasTitle = hasValue$2(title);
2939
+ if (!(hasTitle || showDivider || classNames) && inner === children) {
2940
+ return element;
2941
+ }
2942
+ // ⚠️ Los hijos van como ARGUMENTOS SUELTOS de `cloneElement`, no envueltos en un Fragment:
2943
+ // `withItemIndicator` (que corre después) y `withCollectionItemIds` bajan a los items de una
2944
+ // sección leyendo `props.children` como array o elemento, y un Fragment los escondería — los
2945
+ // items de la sección se quedarían sin checkmark.
2946
+ const parts = [];
2947
+ if (hasTitle) {
2948
+ parts.push(jsxRuntime.jsx(react.Header, { className: react.cn(SECTION_HEADING_CLASS$1, classNames?.heading), children: title }, "aura-section-header"));
2949
+ }
2950
+ parts.push(...(Array.isArray(inner) ? inner : [inner]));
2951
+ if (showDivider) {
2952
+ parts.push(jsxRuntime.jsx(react.Separator, { className: react.cn(SECTION_DIVIDER_CLASS, classNames?.divider) }, "aura-section-divider"));
2953
+ }
2954
+ return $8WNJc$react.cloneElement(element, {
2955
+ className: react.cn(classNames?.base, className) || undefined,
2956
+ // `title` en v3 es el atributo global de HTML: sin retirarlo sale como *tooltip*
2957
+ // nativo sobre toda la sección (el mismo motivo por el que v2 lo quitaba del spread,
2958
+ // `chunk-Q2EVG7SO.mjs`: «removed title from props to avoid browsers showing a tooltip»).
2959
+ title: undefined,
2960
+ showDivider: undefined,
2961
+ classNames: undefined,
2962
+ }, ...parts);
2963
+ }
2964
+ /**
2965
+ * Traduce la API de contenido v2 de cada item/sección de la colección. Va DESPUÉS de
2966
+ * `withCollectionItemIds` (que deriva el `textValue` del texto del item: si la descripción ya
2967
+ * estuviera dentro, la sumaría al texto de búsqueda y del disparador) y ANTES de
2968
+ * `withItemIndicator`.
2969
+ *
2970
+ * Acepta también children como FUNCIÓN (colección dinámica con `items`, la forma de 11 de los 11
2971
+ * `AuraAutocomplete` de beweossmbs): se envuelve la función para traducir lo que devuelva.
2972
+ */
2973
+ function withV2ItemContent(children) {
2974
+ if (typeof children === "function") {
2975
+ const render = children;
2976
+ return ((item) => withV2ItemContent(render(item)));
2977
+ }
2978
+ if (Array.isArray(children)) {
2979
+ return children.map((child) => withV2ItemContent(child));
2980
+ }
2981
+ if (!$8WNJc$react.isValidElement(children)) {
2982
+ return children;
2983
+ }
2984
+ if (children.type === react.ListBoxSection) {
2985
+ return translateSection(children);
2986
+ }
2987
+ if (children.type === react.ListBoxItem) {
2988
+ return translateItem(children);
2989
+ }
2990
+ return children;
2991
+ }
2992
+
2161
2993
  /** Traducciones por defecto en español (mismo literal que la ✕ de la fachada `Input`). */
2162
2994
  const DEFAULT_TRANSLATIONS$f = {
2163
2995
  clearButtonAriaLabel: "Limpiar",
2996
+ emptyContent: "No se encontraron resultados",
2164
2997
  };
2998
+ /*
2999
+ * La lista vacía (TECH-2459, hallazgo beweossmbs 23-sep: 6 `AuraAutocomplete` pasan
3000
+ * `listboxProps.emptyContent` y ninguno se veía).
3001
+ *
3002
+ * v2 (`@heroui/autocomplete@2.3.34`, `dist/chunk-J7SBPTP2.mjs`, leído del tarball):
3003
+ *
3004
+ * allowsEmptyCollection = true, (l.36, default)
3005
+ * listboxProps: mergeProps({
3006
+ * hideEmptyContent: allowsCustomValue && !listboxProps?.emptyContent, (l.160)
3007
+ * emptyContent: "No results found.", (l.161)
3008
+ * }, listboxProps)
3009
+ *
3010
+ * O sea: abría la lista aunque el filtro la dejara vacía, y pintaba un texto — salvo con
3011
+ * `allowsCustomValue` sin texto propio (campo libre: "sin resultados" no es un error ahí). v3 invierte
3012
+ * el default (`allowsEmptyCollection` a `false`: la lista se CIERRA al quedarse vacía) y su `ListBox`
3013
+ * no pinta nada sin `renderEmptyState`. Se reponen las dos cosas con la misma regla de v2; un
3014
+ * `allowsEmptyCollection` explícito del consumidor sigue mandando. El texto por defecto va
3015
+ * traducido (patrón i18n del repo), no el inglés de v2.
3016
+ *
3017
+ * Medidas: las del estado vacío de `Select` (hueco del alto de una opción de v3, gris
3018
+ * `text-foreground-400` del slot `emptyContent` de v2), con el `px-2` del item de v3 porque este
3019
+ * popover no pisa el padding de los items como sí hace el del Select.
3020
+ */
3021
+ const EMPTY_CONTENT_CLASS$1 = "flex min-h-9 w-full items-center px-2 py-1.5 text-start text-foreground-400";
3022
+ /*
3023
+ * El mensaje de error (v2 `errorMessage`, 3 usos en beweossmbs que hoy no se veían). v2 lo pintaba
3024
+ * bajo el campo en el `helperWrapper` del `Input` (`p-1` + `text-tiny text-danger`), solo con el
3025
+ * campo inválido. `FieldError` de v3 ya trae `px-1 text-xs text-danger` (`field-error.css`) y se
3026
+ * muestra solo cuando react-aria da el campo por inválido; el `text-left` es por si el campo vive
3027
+ * en un contenedor centrado. El hueco lo da el `gap-2` del stack, en vez de los 4px del `p-1`: la
3028
+ * diferencia es de 4px y se deja en el ritmo de v3 (§9.1).
3029
+ */
3030
+ const ERROR_MESSAGE_CLASS = "text-left";
3031
+ /*
3032
+ * `isLoading` (v2): el `selectorButton` pasaba a `isLoading` y su spinner ocupaba el sitio del
3033
+ * chevron (`chunk-J7SBPTP2.mjs:168`). ⚠️ En la fachada de v2 eso no se veía con la lupa puesta,
3034
+ * porque esa fachada ocultaba el `selectorButton` cuando había icono (`selectorButton: "!hidden"`,
3035
+ * `git show b39d28f:src/components/autocomplete/AutoComplete.tsx`): el spinner se escondía con él.
3036
+ * Aquí se muestra SIEMPRE, en el sitio del extremo que haya (icono o chevron), porque una prop que
3037
+ * no pinta nada con la configuración por defecto es justo lo que la app reporta como roto.
3038
+ */
3039
+ const SPINNER = (jsxRuntime.jsx(react.Spinner, { "aria-hidden": true, "data-slot": "autocomplete-spinner", size: "sm", color: "current", className: "text-default-400" }));
2165
3040
  /**
2166
3041
  * Clases del wrapper del icono: lo mete DENTRO del recuadro del campo.
2167
3042
  *
@@ -2437,7 +3312,7 @@ const CLEAR_GAP_WITH_TRIGGER_UNDERLINED_CLASS = "me-9";
2437
3312
  * ⚠️ `h-4 w-4 p-0` deja el glifo de 16px ocupando la caja entera; el `[&_svg]:m-0` quita los
2438
3313
  * márgenes negativos de v3 para que el centro del glifo coincida con el centro de la caja.
2439
3314
  */
2440
- const CLEAR_BUTTON_CLASS = "h-4 w-4 min-h-0 shrink-0 rounded-full bg-transparent p-0 text-default-500 hover:bg-transparent [&_svg]:m-0";
3315
+ const CLEAR_BUTTON_CLASS$2 = "h-4 w-4 min-h-0 shrink-0 rounded-full bg-transparent p-0 text-default-500 hover:bg-transparent [&_svg]:m-0";
2441
3316
  const DEFAULT_ICON = (jsxRuntime.jsx(IconComponent, { icon: "solar:magnifer-outline", className: "text-default-400 pointer-events-none shrink-0", size: "sm", "aria-hidden": true }));
2442
3317
  /**
2443
3318
  * AuraAutocomplete genérico reutilizable, migrado al `ComboBox` compound de HeroUI v3 (el
@@ -2462,13 +3337,44 @@ const AuraAutocomplete = ({ label, id, icon, placeholder, className, children, v
2462
3337
  // limpiarlo. Se reenvían tal cual al `ComboBox`, así que para el consumidor no cambia nada:
2463
3338
  // pasar `undefined` es indistinguible de no pasarlos (react-aria decide controlado/no
2464
3339
  // controlado con `!== undefined`).
2465
- isClearable = true, isReadOnly, inputValue, selectedKey, onInputChange, onSelectionChange, translations, radius: _radius, ...props }) => {
3340
+ isClearable = true, isReadOnly, inputValue, selectedKey, onInputChange, onSelectionChange, translations, radius: _radius, isInvalid, errorMessage, listboxProps, isLoading = false, allowsCustomValue, allowsEmptyCollection, isOpen, onOpenChange, popoverProps,
3341
+ // Aceptada y descartada: ver `AuraAutocompleteProps.labelPlacement`.
3342
+ labelPlacement: _labelPlacement, scrollShadowProps, ...props }) => {
2466
3343
  const generatedId = $8WNJc$react.useId();
2467
3344
  const autoId = id || generatedId;
2468
- const resolvedIcon = icon === undefined ? DEFAULT_ICON : icon;
3345
+ // Con `isLoading` el spinner ocupa el extremo (ver `SPINNER`): a efectos de maquetación es el
3346
+ // icono, así que todas las decisiones de hueco de abajo lo tratan como tal.
3347
+ const resolvedIcon = isLoading
3348
+ ? SPINNER
3349
+ : icon === undefined
3350
+ ? DEFAULT_ICON
3351
+ : icon;
2469
3352
  const t = { ...DEFAULT_TRANSLATIONS$f, ...translations };
3353
+ // La regla de v2 para la lista vacía (ver `EMPTY_CONTENT_CLASS`).
3354
+ const hideEmptyContent = Boolean(allowsCustomValue) && listboxProps?.emptyContent == null;
3355
+ const emptyContent = listboxProps?.emptyContent ?? t.emptyContent;
2470
3356
  /** El `<input>` real, para devolverle el foco tras limpiar (ver `handleClear`). */
2471
3357
  const inputRef = $8WNJc$react.useRef(null);
3358
+ /*
3359
+ * `isOpen` controlado (ver `AuraAutocompleteProps.isOpen`). La fachada sigue el estado de
3360
+ * apertura de react-aria por su `onOpenChange` y decide con él si el popover se ve: visible solo
3361
+ * si react-aria lo tiene abierto Y la app no lo ha cerrado. `ComboBox.Popover` es el `Popover`
3362
+ * de react-aria-components, que con `isOpen` propio usa ese valor en vez del estado del
3363
+ * `ComboBox` (`Popover.mjs`: `props.isOpen != null ? localState : contextState`).
3364
+ */
3365
+ const [ariaOpen, setAriaOpen] = $8WNJc$react.useState(false);
3366
+ const handleOpenChange = (open, menuTrigger) => {
3367
+ setAriaOpen(open);
3368
+ onOpenChange?.(open, menuTrigger);
3369
+ };
3370
+ const handleInputChange = (value) => {
3371
+ // La lista está abierta para react-aria pero la app la cerró: react-aria no volverá a avisar
3372
+ // (para él ya está abierta), así que la fachada pide abrir como hacía v2 al teclear.
3373
+ if (isOpen === false && ariaOpen) {
3374
+ onOpenChange?.(true, "input");
3375
+ }
3376
+ onInputChange?.(value);
3377
+ };
2472
3378
  // `data-visible` de v2, traducido a montar/no montar: `!!state.selectedItem ||
2473
3379
  // state.inputValue?.length > 0` (`chunk-NJJHRP6D.mjs:273`), y apagado por `isDisabled`/
2474
3380
  // `isReadOnly` como en el cálculo de `isClearable` del hook (línea 20 del mismo fichero).
@@ -2502,7 +3408,7 @@ isClearable = true, isReadOnly, inputValue, selectedKey, onInputChange, onSelect
2502
3408
  onInputChange?.("");
2503
3409
  inputRef.current?.focus();
2504
3410
  };
2505
- 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: [
3411
+ return (jsxRuntime.jsxs(react.ComboBox, { id: autoId, isDisabled: isDisabled, isReadOnly: isReadOnly, inputValue: inputValue, selectedKey: selectedKey, onInputChange: handleInputChange, onSelectionChange: onSelectionChange, onOpenChange: handleOpenChange, isInvalid: isInvalid, allowsCustomValue: allowsCustomValue, allowsEmptyCollection: allowsEmptyCollection ?? !hideEmptyContent, className: [FIELD_STACK_CLASS$2, className].filter(Boolean).join(" "), ...props, children: [label && jsxRuntime.jsx(react.Label, { className: LABEL_CLASS$3, children: label }), jsxRuntime.jsxs(react.ComboBox.InputGroup, { className: [
2506
3412
  SIZE_CLASS$2[size],
2507
3413
  PADDING_CLASS$1[size],
2508
3414
  variant === "underlined" ? UNDERLINED_INNER_CLASS[size] : "",
@@ -2551,7 +3457,7 @@ isClearable = true, isReadOnly, inputValue, selectedKey, onInputChange, onSelect
2551
3457
  * su `aria-label` traducido, y alcanzable con teclado como la ✕ de `Input`.
2552
3458
  */
2553
3459
  slot: null, "aria-label": t.clearButtonAriaLabel, className: [
2554
- CLEAR_BUTTON_CLASS,
3460
+ CLEAR_BUTTON_CLASS$2,
2555
3461
  resolvedIcon != null
2556
3462
  ? CLEAR_GAP_WITH_ICON_CLASS
2557
3463
  : variant === "underlined"
@@ -2565,7 +3471,19 @@ isClearable = true, isReadOnly, inputValue, selectedKey, onInputChange, onSelect
2565
3471
  isDisabled ? ICON_DISABLED_CLASS : "",
2566
3472
  ]
2567
3473
  .filter(Boolean)
2568
- .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) }) })] }));
3474
+ .join(" "), children: resolvedIcon })), jsxRuntime.jsx(react.ComboBox.Trigger, { className: resolvedIcon != null ? "hidden" : undefined })] }), isInvalid && errorMessage != null && errorMessage !== false && (jsxRuntime.jsx(react.FieldError, { className: ERROR_MESSAGE_CLASS, children: errorMessage })), jsxRuntime.jsx(react.ComboBox.Popover, { ...popoverProps, isOpen: isOpen === undefined ? undefined : isOpen && ariaOpen, children: jsxRuntime.jsx(react.ListBox, { autoFocus: "first",
3475
+ // `scrollShadowProps` de v2 al contenedor con scroll (el `ListBox`): la clase
3476
+ // de la app va al final y gana por `cn`, y `overflow-y-auto` para que un
3477
+ // `max-h-*` recorte desplazando y no desbordando.
3478
+ className: scrollShadowProps?.className
3479
+ ? react.cn(OPCIONES_CLASS, "overflow-y-auto", scrollShadowProps.className)
3480
+ : OPCIONES_CLASS, style: scrollShadowProps?.style, renderEmptyState: hideEmptyContent
3481
+ ? undefined
3482
+ : () => jsxRuntime.jsx("div", { className: EMPTY_CONTENT_CLASS$1, children: emptyContent }), children: withV2ItemContent(
3483
+ // El normalizador de `utils/` está tipado solo para `ReactNode`; con una
3484
+ // función la devuelve tal cual (`isValidElement` es falso), así que el cast
3485
+ // solo alinea el tipo con lo que ya pasa en ejecución.
3486
+ withCollectionItemIds(children)) }) })] }));
2569
3487
  };
2570
3488
 
2571
3489
  /**
@@ -8409,6 +9327,25 @@ const DAY_PERIOD_SEGMENT_CLASS = "ms-0.5";
8409
9327
  * el texto (`rgb(243,18,96)`). Deuda a reportar aparte, no de este arreglo.
8410
9328
  */
8411
9329
  const FIELD_FOCUS_CLASS$2 = "ring-0 ring-offset-0";
9330
+ /*
9331
+ * TECH-2459 · hallazgo beweossmbs 23-sep — `startContent`/`endContent` de v2, repuestas DENTRO de
9332
+ * la caja del campo.
9333
+ *
9334
+ * v2 (`@heroui/date-input@2.3.32`, `dist/time-input.js:319-329`) las pintaba en el `innerWrapper`,
9335
+ * dentro del `inputWrapper` (la caja con borde): `startContent`, los segmentos y `endContent`, en
9336
+ * ese orden. El `tv` de v2 (`@heroui/theme@2.4.15`, `date-input.js`) da a ese envoltorio
9337
+ * `flex items-center … w-full gap-x-2` y al `inputWrapper` `px-3 gap-3`. v1 no aceptaba las
9338
+ * props: el alta de citas de beweossmbs (`datetime-section.component.tsx`) pone ahí las flechas ±
9339
+ * de la rejilla de horas y desaparecían.
9340
+ *
9341
+ * En v3 el sitio equivalente son `TimeField.Prefix`/`TimeField.Suffix` (`DateInputGroupPrefix`/
9342
+ * `Suffix`), hijos del mismo `TimeField.Group`. Sus márgenes ya son los de v2 (`ms-3`/`me-3` = el
9343
+ * `px-3` del wrapper, y el input pasa a `pe-2`/`ps-2` = el `gap-x-2`), así que solo se añade una
9344
+ * cosa: `pointer-events-auto`. `.date-input-group__prefix/__suffix` de `@heroui/styles` los
9345
+ * declara `pointer-events-none` (pensados para un icono decorativo) y solo `.date-picker` los
9346
+ * reactiva; en un `TimeField` un botón puesto ahí no recibiría el clic. v2 no tenía esa regla.
9347
+ */
9348
+ const CONTENT_SLOT_CLASS = "pointer-events-auto";
8412
9349
  /**
8413
9350
  * Implementación común de los dos nombres exportados. La única diferencia entre `TimeInput` y
8414
9351
  * `TimeInputComponent` es el valor por defecto de `labelPlacement`; una `labelPlacement` explícita
@@ -8419,16 +9356,16 @@ function TimeInputBase({ label, id, className, labelPlacement,
8419
9356
  // `dist/use-time-input.js:58`, `shouldForceLeadingZeros = true`), así que la referencia v2
8420
9357
  // muestra "09:00 AM"; React Aria por sí solo deja "9:00 AM". Es una prop de React Aria que v3
8421
9358
  // sigue aceptando (`AriaTimeFieldProps`), solo cambió el default.
8422
- shouldForceLeadingZeros = true, ...props }) {
9359
+ shouldForceLeadingZeros = true, startContent, endContent, ...props }) {
8423
9360
  const generatedId = $8WNJc$react.useId();
8424
9361
  const autoId = id || generatedId;
8425
9362
  return (jsxRuntime.jsxs(react.TimeField, { id: autoId,
8426
9363
  // `cn` (tailwind-merge) y no una concatenación: el consumidor tiene que poder pisar la
8427
9364
  // colocación y el ancho (`ScheduleRow` pasa `w-full`). Todas las clases de aquí son
8428
9365
  // vocabulario v3 válido, así que tailwind-merge las resuelve bien.
8429
- 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.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"
8430
- ? DAY_PERIOD_SEGMENT_CLASS
8431
- : undefined })) }) })] }));
9366
+ 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"
9367
+ ? DAY_PERIOD_SEGMENT_CLASS
9368
+ : undefined })) }), endContent !== undefined && endContent !== null && (jsxRuntime.jsx(react.TimeField.Suffix, { className: CONTENT_SLOT_CLASS, children: endContent }))] })] }));
8432
9369
  }
8433
9370
  /**
8434
9371
  * `TimeInput` — el nombre que en v2 era el crudo de HeroUI: etiqueta ENCIMA del campo
@@ -8798,6 +9735,33 @@ const SIZE_TEXT_CLASS$1 = {
8798
9735
  // `min-w-0` es obligatorio: un ítem flex tiene `min-width: auto`, o sea no baja de su `min-content`,
8799
9736
  // y sin eso el texto desborda el trigger en vez de truncar con elipsis.
8800
9737
  const VALUE_ROOM_CLASS = "me-6 min-w-0 truncate";
9738
+ /*
9739
+ * `isMultiline` (v2). Literal del `tv` (`@heroui/theme@2.4.15`, `dist/components/select.js:255-263`):
9740
+ *
9741
+ * isMultiline: { true: { label: "relative", trigger: "!h-auto" }, false: { value: "truncate" } }
9742
+ *
9743
+ * O sea: el disparador deja de tener alto fijo y el valor deja de truncarse. El `min-h-*` del
9744
+ * tamaño se conserva (v2 también: el `!h-auto` solo pisaba el `h-*`), así que en una línea mide lo
9745
+ * mismo. El `py-1.5` es nuestro y es obligatorio aquí: `TRIGGER_ALIGN_CLASS` pone `py-0` porque con
9746
+ * alto fijo el centrado lo hace `items-center`; con alto automático, sin relleno vertical la
9747
+ * segunda línea tocaría el borde. 6px + 20px de línea + 6px = los 32px del `sm`, así que en una
9748
+ * sola línea no cambia nada. Van DESPUÉS en el `cn`, que resuelve `h-8`→`h-auto` y `py-0`→`py-1.5`.
9749
+ */
9750
+ const MULTILINE_TRIGGER_CLASS = "h-auto py-1.5";
9751
+ const MULTILINE_VALUE_ROOM_CLASS = "me-6 min-w-0 whitespace-normal";
9752
+ /*
9753
+ * `startContent` del disparador (v2). En v2 iba dentro del `innerWrapper`, delante del valor, con
9754
+ * el `gap-1.5` de ese slot (`select.js:100`). Aquí el disparador es el flex y el hueco va como
9755
+ * margen de la pieza, para no tocar el `gap` que v3 le pone al disparador entero.
9756
+ */
9757
+ const START_CONTENT_CLASS = "me-1.5 flex shrink-0 items-center";
9758
+ /*
9759
+ * `isLoading` (v2): el spinner SUSTITUYE al chevron en el mismo sitio. Literales de v2
9760
+ * (`@heroui/select@2.4.33`, `use-select`: `getSpinnerProps` → `color: "current"`, `size: "sm"`;
9761
+ * slot `spinner: "absolute end-3"`). Se centra en vertical como el `Select.Indicator` de v3
9762
+ * (`inset-y-0 my-auto`) y toma el color del chevron (`text-field-placeholder`, `select.css`).
9763
+ */
9764
+ const SPINNER_CLASS = "pointer-events-none absolute inset-y-0 end-3 my-auto text-field-placeholder";
8801
9765
  /*
8802
9766
  * EL ESTADO VACÍO DEL DESPLEGABLE (TECH-2459 · F5 · defecto reportado por el usuario y medido por
8803
9767
  * la sesión de backoffice: «se pulsa el campo y no pasa nada»).
@@ -8854,7 +9818,98 @@ const VALUE_ROOM_CLASS = "me-6 min-w-0 truncate";
8854
9818
  */
8855
9819
  const DEFAULT_TRANSLATIONS$8 = {
8856
9820
  emptyContent: "No hay opciones",
9821
+ searchPlaceholder: "Buscar…",
9822
+ // Mismo literal que la lista vacía de `AuraAutocomplete`: es el mismo caso (un filtro por texto
9823
+ // que no encuentra nada), así que las dos fachadas lo dicen igual.
9824
+ noResults: "No se encontraron resultados",
9825
+ // Mismo literal que la ✕ de `Input` y de `AuraAutocomplete`.
9826
+ clearButtonAriaLabel: "Limpiar",
8857
9827
  };
9828
+ /*
9829
+ * EL BUSCADOR Y LA ✕ (TECH-2459, hallazgo beweossmbs 23-sep): `isSearchable` e `isClearable`.
9830
+ *
9831
+ * Con cualquiera de los dos la fachada cambia de RAÍZ: monta el compound `Autocomplete` de v3 en
9832
+ * vez de `Select`. No es un compound distinto por dentro —`AutocompleteRoot` ES el `Select` de
9833
+ * react-aria (`@heroui/react/dist/components/autocomplete/autocomplete.js:50`, `jsx(Select, …)`),
9834
+ * así que `value`/`onChange`/`selectionMode`/`disabledKeys` y la colección funcionan igual y la
9835
+ * traducción de la API v2 de abajo no cambia ni una línea—, pero su disparador sí:
9836
+ *
9837
+ * - `Autocomplete.Trigger` es un `Group` (un `<div>`), no un `<button>`. Es lo que permite meter la
9838
+ * ✕ DENTRO del campo sin anidar dos botones (trampa nº 4 de `auraui-components`). El botón que
9839
+ * recibe el foco es el del `Autocomplete.Indicator`, que consume el `ButtonContext` del Select
9840
+ * (`autocomplete.js`, `jsx(Button, { children: indicator })`): Tab llega al chevron y desde ahí
9841
+ * las flechas/Enter abren la lista, igual que en el `Select` de siempre.
9842
+ * - `Autocomplete.Filter` (el `Autocomplete` de react-aria) conecta un `SearchField` con el
9843
+ * `ListBox`: el texto filtra la colección y las flechas mueven el foco virtual de la lista sin
9844
+ * salir del buscador. Es la receta oficial «Migrating to v3 Autocomplete (searchable select)»
9845
+ * de la guía de migración, con `useFilter({ sensitivity: "base" })`: sin distinguir mayúsculas
9846
+ * ni acentos («cancun» encuentra «Cancún»).
9847
+ *
9848
+ * Sin ninguna de las dos props la fachada sigue montando `Select`, exactamente como antes: los
9849
+ * 107 `Select` de beweossmbs que no las pasan no cambian ni un nodo.
9850
+ *
9851
+ * Por qué la ✕ va también por aquí y no sobre el `Select`: el disparador de `Select` es un
9852
+ * `<button>` (no admite otro dentro) y la fachada no alcanza el estado de react-aria para vaciar la
9853
+ * selección de un campo no controlado (`SelectStateContext` no lo reexporta `@heroui/react`, e
9854
+ * importarlo de `react-aria-components` metería una SEGUNDA copia del contexto en el bundle, que no
9855
+ * casaría con la de HeroUI). `Autocomplete.ClearButton` sí lo alcanza: vacía la selección con
9856
+ * `state.selectionManager.setSelectedKeys(new Set())` y llama a `onClear`.
9857
+ */
9858
+ /** Hueco del valor cuando la ✕ va a su derecha: el hueco del chevron lo reserva la ✕. */
9859
+ const VALUE_ROOM_CLEARABLE_CLASS = "me-1 min-w-0 truncate";
9860
+ /*
9861
+ * La ✕. `.autocomplete__clear-button` de v3 ya la pinta como v2 (glifo sin caja, `bg-transparent`,
9862
+ * gris `text-muted`) y la esconde con `opacity-0` cuando no hay nada elegido. v2 no la montaba sin
9863
+ * selección (`@heroui/select@2.4.33`, `chunk-BJWIL32D.mjs:64`: `isClearable &&
9864
+ * state.selectedItems?.length`), así que aquí se retira del flujo en ese estado en vez de dejar un
9865
+ * hueco invisible de 20px.
9866
+ *
9867
+ * `me-5`: el chevron es `absolute end-3` (12px del borde, 16px de ancho) y la ✕ va en flujo, que
9868
+ * acaba en el relleno del trigger (`px-3`, 12px). Sin margen caería ENCIMA del chevron; con 20px su
9869
+ * canto derecho queda a 4px del izquierdo del chevron.
9870
+ */
9871
+ const CLEAR_BUTTON_CLASS$1 = "z-10 me-5 data-[empty=true]:hidden";
9872
+ /*
9873
+ * EL FOCO DEL TECLADO (hallazgo del coordinador con Playwright, 23-sep). En la raíz `Autocomplete`
9874
+ * el único elemento tabulable es el botón del chevron —el `Button` de react-aria que monta
9875
+ * `Autocomplete.Indicator`, sin clase propia ni forma de pasarle una—, y en el flujo mide **0×0**:
9876
+ * su único hijo es el SVG, que es `absolute`. El foco caía en una caja vacía (el borde del disparador
9877
+ * sí cambiaba, por `focus-within`, pero el elemento enfocado no tenía caja para un lector de
9878
+ * pantalla, un magnificador o un anillo propio).
9879
+ *
9880
+ * Se estira ese botón sobre el disparador entero (`absolute inset-0`, mismo radio): el elemento
9881
+ * enfocado pasa a medir lo mismo que el disparador, como el `<button>` del `Select` normal, y el
9882
+ * SVG sigue en su sitio porque su contenedor posicionado cubre la misma caja. Pulsar en cualquier
9883
+ * punto sigue abriendo (el `Group` ya abría al pulsar, y el botón ya estaba DENTRO del grupo).
9884
+ * La ✕ va por encima (`z-10` en `CLEAR_BUTTON_CLASS`) para seguir recibiendo su clic.
9885
+ *
9886
+ * Se apunta por estructura porque es la única vía: `button:not([data-slot])` es ese botón, ya que
9887
+ * la ✕ lleva `data-slot="autocomplete-clear-button"`.
9888
+ */
9889
+ const INDICATOR_BUTTON_OVERLAY_CLASS = [
9890
+ "[&>button:not([data-slot])]:absolute",
9891
+ "[&>button:not([data-slot])]:inset-0",
9892
+ "[&>button:not([data-slot])]:rounded-[inherit]",
9893
+ "[&>button:not([data-slot])]:outline-none",
9894
+ "[&>button:not([data-slot])]:cursor-[inherit]",
9895
+ ].join(" ");
9896
+ /*
9897
+ * El foco en el disparador `Group`. El `FOCUS_BORDER_CLASS` del Select casa `aria-expanded` y
9898
+ * `:focus` del `<button>`; aquí el disparador es un `<div>` que no lleva ninguno de los dos: el foco
9899
+ * lo tiene el botón del chevron, DENTRO (`focus-within`), y el estado abierto lo escribe v3 en la
9900
+ * RAÍZ (`data-open="true"` en `div.autocomplete`), así que va por un `group` con nombre.
9901
+ */
9902
+ const FOCUS_BORDER_GROUP_CLASS = "focus-within:border-primary-500! group-data-[open=true]/aura-select:border-primary-500!";
9903
+ /*
9904
+ * El buscador del desplegable. `.autocomplete__popover` de v3 ya lo coloca (`[data-slot=
9905
+ * "search-field"] { px-3 py-1 }`, `pt-2` en el popover, `overflow-hidden` + lista con su propio
9906
+ * scroll, de modo que el buscador no se va al desplazar la lista). Solo se retira el anillo de
9907
+ * foco de v3 (`status-focused-field` → `ring-2`), por lo mismo que en todos los campos de la
9908
+ * librería (`FIELD_FOCUS_CLASS`): el foco lo marca el borde, en primario.
9909
+ */
9910
+ const SEARCH_GROUP_CLASS = "w-full ring-0 ring-offset-0 outline-0 focus-within:border-primary-500";
9911
+ /** El `ListBox` es el contenedor que se desplaza cuando el consumidor le fija alto. */
9912
+ const LISTBOX_SCROLL_CLASS = "overflow-y-auto";
8858
9913
  // Medidas del estado vacío. Se toman las del ITEM de v3 (`@heroui/styles`,
8859
9914
  // `dist/components/list-box-item.css:6`: `min-h-9 … px-2 py-1.5`) y no las del slot `emptyContent`
8860
9915
  // de v2, y es una decisión medida: el literal de v2 era
@@ -9126,7 +10181,9 @@ size = "md", variant = "bordered", radius = "md",
9126
10181
  //
9127
10182
  // Por eso este componente sigue sin `SelectTranslations`: la corrección consiste precisamente en
9128
10183
  // que la librería no aporte NINGÚN texto propio, no en traducir el que había.
9129
- placeholder = "", selectionMode = "single", selectedKeys, defaultSelectedKeys, onSelectionChange, disabledKeys, isInvalid = false, isRequired = false, isDisabled = false,
10184
+ placeholder = "", selectionMode = "single", selectedKeys, defaultSelectedKeys, onSelectionChange, disabledKeys, isInvalid = false, isRequired = false,
10185
+ // Alias nativo de `isRequired` (ver `SelectProps.required`).
10186
+ required = false, isDisabled = false,
9130
10187
  // `fullWidth` por defecto a `true`: HeroUI v2 lo traía en sus `defaultVariants`
9131
10188
  // (verificado en el paquete v2 compilado, `dist/components/input.js`:
9132
10189
  // `defaultVariants: { variant:"flat", color:"default", size:"md", fullWidth:true, … }`, con
@@ -9134,42 +10191,139 @@ placeholder = "", selectionMode = "single", selectedKeys, defaultSelectedKeys, o
9134
10191
  // `undefined` y el campo dejaba de llenar su contenedor: en una fila flex se encogía al ancho
9135
10192
  // de su contenido (medido por S2: 208px dentro de una fila de 592, 336px de hueco). En una
9136
10193
  // columna no se notaba porque el `stretch` del flex lo tapaba — pero afecta a cualquier app.
9137
- fullWidth = true, isOpen, defaultOpen, onOpenChange, className, classNames, translations = {}, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, "aria-details": ariaDetails, }) => {
10194
+ fullWidth = true, isLoading = false,
10195
+ // Aceptada y descartada: ver `SelectProps.isVirtualized`.
10196
+ isVirtualized: _isVirtualized, disallowEmptySelection = false, isMultiline = false, renderValue, startContent, listboxProps, onChange,
10197
+ // Aceptada y descartada: ver `SelectProps.labelPlacement`.
10198
+ labelPlacement: _labelPlacement, scrollShadowProps, items, isSearchable = false, searchPlaceholder, isClearable = false, onClear, closeOnSelect, isOpen, defaultOpen, onOpenChange, className, classNames, translations = {}, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, "aria-details": ariaDetails, }) => {
9138
10199
  const generatedId = $8WNJc$react.useId();
9139
10200
  const selectId = id || generatedId;
9140
- const requiredField = isRequired;
10201
+ const requiredField = isRequired || required;
9141
10202
  const t = { ...DEFAULT_TRANSLATIONS$8, ...translations };
10203
+ // Raíz `Autocomplete` (buscador y/o ✕) o `Select` de siempre: ver el bloque «EL BUSCADOR Y LA ✕».
10204
+ const usesAutocompleteShell = isSearchable || isClearable;
10205
+ // Filtro de react-aria sin mayúsculas ni acentos. El hook va fuera de la condición (reglas de
10206
+ // hooks); sin `isSearchable` no se usa.
10207
+ const { contains } = react.useFilter({ sensitivity: "base" });
10208
+ // Texto del buscador, en la fachada para saber qué mensaje poner en la lista vacía y para
10209
+ // vaciarlo al cerrar (abrir de nuevo = lista completa, que es lo que hacía el buscador manual
10210
+ // de `timezone-search-select` con su `onOpenChange`).
10211
+ const [searchText, setSearchText] = $8WNJc$react.useState("");
10212
+ // v2 pintaba `listboxProps.emptyContent` en el hueco de la lista vacía; gana a la traducción,
10213
+ // que es el default de la fachada (el de v2 era el literal inglés "No items."). Con el buscador
10214
+ // escrito, la lista vacía significa «sin resultados», no «sin opciones».
10215
+ const emptyContent = isSearchable && searchText.trim() !== ""
10216
+ ? t.noResults
10217
+ : (listboxProps?.emptyContent ?? t.emptyContent);
10218
+ const resolvedSearchPlaceholder = searchPlaceholder ?? t.searchPlaceholder;
10219
+ const handleOpenChange = (open) => {
10220
+ if (!open) {
10221
+ setSearchText("");
10222
+ }
10223
+ onOpenChange?.(open);
10224
+ };
10225
+ // Estado propio SOLO para `multiple` + `disallowEmptySelection` sin controlar: para rechazar el
10226
+ // cambio que vaciaría la selección, la fachada tiene que ser dueña del valor. En los demás casos
10227
+ // no se lee (el hook va fuera de la condición por las reglas de hooks).
10228
+ const [ownMultiple, setOwnMultiple] = $8WNJc$react.useState(() => toArray(defaultSelectedKeys) ?? []);
9142
10229
  // Mismo criterio que el `group-data-[has-helper=true]` de v2: cuenta como "con ayuda" el campo
9143
10230
  // que efectivamente PINTA una descripción o un mensaje de error, no el que solo recibe la prop.
9144
10231
  const hasHelper = Boolean((description && !isInvalid) || (isInvalid && errorMessage));
9145
- 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],
10232
+ 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],
9146
10233
  // En v2 el estado inválido MANDABA también con el campo enfocado
9147
10234
  // (`group-data-[focus=true]:!border-danger`), así que el borde de foco no se aplica cuando
9148
10235
  // el campo está en error: se queda el `danger`.
9149
- isInvalid ? INVALID_VARIANT_CLASS[variant] : FOCUS_BORDER_CLASS, FIELD_FOCUS_CLASS$1, FIELD_SHADOW_CLASS$1,
10236
+ isInvalid
10237
+ ? INVALID_VARIANT_CLASS[variant]
10238
+ : usesAutocompleteShell
10239
+ ? FOCUS_BORDER_GROUP_CLASS
10240
+ : FOCUS_BORDER_CLASS, FIELD_FOCUS_CLASS$1, FIELD_SHADOW_CLASS$1, usesAutocompleteShell ? INDICATOR_BUTTON_OVERLAY_CLASS : "",
9150
10241
  // Incondicional, como el slot `trigger` de v2 (`"relative px-3 gap-3 w-full inline-flex …"`,
9151
10242
  // sin variante que lo condicione): el disparador llena siempre su raíz, y es la raíz la que
9152
10243
  // responde a `fullWidth` y al ancho que pase el consumidor. v3 hace lo mismo por su cuenta
9153
10244
  // con `.select__trigger--full-width`, pero esa regla vive en la capa `components` y la pisa
9154
10245
  // cualquier utilidad.
9155
10246
  "w-full", classNames?.trigger);
10247
+ // `id`/`textValue` desde la `key` de React: ver `utils/collection-items.ts` — sin esto un select
10248
+ // múltiple no pinta lo elegido y uno controlado arranca vacío. `withV2ItemContent` traduce
10249
+ // `description`/`startContent`/`endContent`/`classNames` del item y `title`/`showDivider` de la
10250
+ // sección a la anatomía de v3 (ver `_internal/v2-collection.tsx` para el orden), y
10251
+ // `withItemIndicator` monta el checkmark del seleccionado, que en v3 es un subcomponente aparte
10252
+ // (ver `_internal/with-item-indicator.tsx`, y ahí por qué va en este orden).
10253
+ const decorate = (nodes) => withItemIndicator(withV2ItemContent(withCollectionItemIds(nodes)));
10254
+ // Colección dinámica (`items` + función, la forma de `Profile.tsx`). La función se envuelve para
10255
+ // decorar lo que devuelva, y el paso de `withCollectionItemIds` importa aquí MÁS que en la forma
10256
+ // estática: react-aria toma la clave de una colección dinámica de `rendered.props.id ??
10257
+ // item.key ?? item.id`, nunca de la `key` de React. Sin reponer el `id` desde la `key`, las
10258
+ // opciones de `Profile.tsx` (objetos `{ value: "es", id: 1 }` con `<SelectItem key={value}>`)
10259
+ // se llamarían `1`/`2` y ni `defaultSelectedKeys={["es"]}` casaría ni `onSelectionChange`
10260
+ // devolvería la clave de la app.
10261
+ const collection = typeof children === "function"
10262
+ ? (item) => decorate(children(item))
10263
+ : decorate(children);
10264
+ const listBox = (jsxRuntime.jsx(react.ListBox
10265
+ // v2 `items`: react-aria recorre la colección y llama a la función de `children`.
10266
+ , {
10267
+ // v2 `items`: react-aria recorre la colección y llama a la función de `children`.
10268
+ items: items,
10269
+ // v2: `classNames.listboxWrapper` y `scrollShadowProps.className` iban al mismo slot
10270
+ // (`cn(classNames.listboxWrapper, scrollShadowProps.className)`, `chunk-YFRCT47S.mjs:
10271
+ // 321-331`), en ese orden.
10272
+ className: classNames?.listboxWrapper || scrollShadowProps?.className
10273
+ ? react.cn(LISTBOX_SCROLL_CLASS, classNames?.listboxWrapper, scrollShadowProps?.className)
10274
+ : undefined, style: scrollShadowProps?.style,
10275
+ // El hueco que v2 llenaba con su `emptyContent` (ver el bloque de
10276
+ // `DEFAULT_TRANSLATIONS`). `renderEmptyState` es la vía de v3: el `ListBox` de
10277
+ // react-aria solo pinta algo con la colección vacía si se le pasa, y envuelve el
10278
+ // resultado en un `<div role="option" style="display:contents">`
10279
+ // (`react-aria-components/dist/private/ListBox.mjs:222-228`), o sea que la caja que se
10280
+ // ve es la nuestra.
10281
+ renderEmptyState: () => (jsxRuntime.jsx("div", { className: EMPTY_CONTENT_CLASS, children: emptyContent })), children: collection }));
10282
+ // Las piezas del disparador son las mismas en las dos raíces; solo cambia de qué compound salen
10283
+ // (ver el bloque «EL BUSCADOR Y LA ✕»). `Autocomplete.Value` y `Select.Value` son el mismo
10284
+ // `SelectValue` de react-aria con otra clase base, así que reciben las mismas props.
10285
+ const ValuePart = usesAutocompleteShell
10286
+ ? react.Autocomplete.Value
10287
+ : react.Select.Value;
10288
+ const triggerContent = (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [startContent != null && startContent !== false && (jsxRuntime.jsx("span", { "data-slot": "start-content", className: START_CONTENT_CLASS, children: startContent })), jsxRuntime.jsx(ValuePart, { className: react.cn(SIZE_TEXT_CLASS$1[size], isMultiline
10289
+ ? MULTILINE_VALUE_ROOM_CLASS
10290
+ : isClearable
10291
+ ? VALUE_ROOM_CLEARABLE_CLASS
10292
+ : VALUE_ROOM_CLASS, HIDE_ITEM_EXTRAS_CLASS, isInvalid ? INVALID_TEXT_CLASS$1 : ""), children: renderValue
10293
+ ? // `renderValue` de v2 (`chunk-BJWIL32D.mjs`): sin selección, el placeholder;
10294
+ // con selección, la función con los items en la forma de v2. El nodo de la
10295
+ // colección de v3 trae los mismos campos que el de v2, así que el mapeo es
10296
+ // 1:1. `defaultChildren` es aquí el placeholder (o `""`, ver arriba).
10297
+ ({ state, isPlaceholder, defaultChildren }) => {
10298
+ if (isPlaceholder) {
10299
+ return defaultChildren;
10300
+ }
10301
+ const items = state.selectedItems.map((item) => ({
10302
+ key: item.key,
10303
+ data: item.value,
10304
+ type: item.type,
10305
+ props: item.props,
10306
+ textValue: item.textValue,
10307
+ rendered: item.rendered,
10308
+ "aria-label": item["aria-label"],
10309
+ }));
10310
+ return renderValue(items);
10311
+ }
10312
+ : undefined }), isClearable && (jsxRuntime.jsx(react.Autocomplete.ClearButton, { "aria-label": t.clearButtonAriaLabel, className: CLEAR_BUTTON_CLASS$1 })), usesAutocompleteShell ? (
10313
+ /* En el `Autocomplete` el chevron es el BOTÓN que recibe el foco (ver el bloque
10314
+ «EL BUSCADOR Y LA ✕»), así que el spinner va DENTRO de él en vez de sustituirlo:
10315
+ sin el botón el campo no se alcanzaría con el teclado. `Autocomplete.Indicator` clona
10316
+ su hijo con la clase del indicador (`absolute inset-y-0 end-2 my-auto`), así que el
10317
+ spinner cae en el sitio del chevron sin clase propia. */
10318
+ jsxRuntime.jsx(react.Autocomplete.Indicator, { className: "end-3", children: isLoading ? (jsxRuntime.jsx(react.Spinner, { "aria-hidden": true, "data-slot": "select-spinner", size: "sm", color: "current" })) : undefined })) : isLoading ? (jsxRuntime.jsx(react.Spinner, { "aria-hidden": true, "data-slot": "select-spinner", size: "sm", color: "current", className: SPINNER_CLASS })) : (
10319
+ /* `.select__indicator` de v3 va a `end-2` (8px del borde interior); el
10320
+ `selectorIcon` de v2 iba a `end-3` (12px). Medido en los PNG: la tinta del chevron
10321
+ acaba a 17px del borde derecho en la referencia y a 13px con el valor de v3 — 4px
10322
+ de más. */
10323
+ jsxRuntime.jsx(react.Select.Indicator, { className: "end-3" }))] }));
9156
10324
  const inner = (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [label && (jsxRuntime.jsx(react.Label, { className: react.cn(LABEL_CLASS$1, hasHelper
9157
10325
  ? LABEL_OFFSET_HELPER_CLASS[size]
9158
- : 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
9159
- // El hueco que v2 llenaba con su `emptyContent` (ver el bloque de
9160
- // `DEFAULT_TRANSLATIONS`). `renderEmptyState` es la vía de v3: el `ListBox` de
9161
- // react-aria solo pinta algo con la colección vacía si se le pasa, y envuelve
9162
- // el resultado en un `<div role="option" style="display:contents">`
9163
- // (`react-aria-components/dist/private/ListBox.mjs:222-228`), o sea que la caja
9164
- // que se ve es la nuestra.
9165
- , {
9166
- // El hueco que v2 llenaba con su `emptyContent` (ver el bloque de
9167
- // `DEFAULT_TRANSLATIONS`). `renderEmptyState` es la vía de v3: el `ListBox` de
9168
- // react-aria solo pinta algo con la colección vacía si se le pasa, y envuelve
9169
- // el resultado en un `<div role="option" style="display:contents">`
9170
- // (`react-aria-components/dist/private/ListBox.mjs:222-228`), o sea que la caja
9171
- // que se ve es la nuestra.
9172
- 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 }))] }));
10326
+ : LABEL_OFFSET_CLASS[size], isInvalid ? INVALID_LABEL_CLASS$1 : "", classNames?.label), children: label })), usesAutocompleteShell ? (jsxRuntime.jsx(react.Autocomplete.Trigger, { className: triggerClassName, children: triggerContent })) : (jsxRuntime.jsx(react.Select.Trigger, { className: triggerClassName, children: triggerContent })), description && !isInvalid && (jsxRuntime.jsx(react.Description, { className: `${HELPER_CLASS$1} text-left`, children: description })), usesAutocompleteShell ? (jsxRuntime.jsx(react.Autocomplete.Popover, { className: classNames?.popoverContent, children: isSearchable ? (jsxRuntime.jsxs(react.Autocomplete.Filter, { filter: contains, inputValue: searchText, onInputChange: setSearchText, children: [jsxRuntime.jsx(react.SearchField, { autoFocus: true, "aria-label": resolvedSearchPlaceholder, fullWidth: true, children: jsxRuntime.jsxs(react.SearchField.Group, { className: SEARCH_GROUP_CLASS, children: [jsxRuntime.jsx(react.SearchField.SearchIcon, {}), jsxRuntime.jsx(react.SearchField.Input, { placeholder: resolvedSearchPlaceholder })] }) }), listBox] })) : (listBox) })) : (jsxRuntime.jsx(react.Select.Popover, { className: classNames?.popoverContent, children: listBox })), isInvalid && errorMessage && (jsxRuntime.jsx(react.FieldError, { className: `${HELPER_CLASS$1} text-left`, children: errorMessage }))] }));
9173
10327
  const commonProps = {
9174
10328
  id: selectId,
9175
10329
  name,
@@ -9195,7 +10349,14 @@ fullWidth = true, isOpen, defaultOpen, onOpenChange, className, classNames, tran
9195
10349
  isDisabled,
9196
10350
  isOpen,
9197
10351
  defaultOpen,
9198
- onOpenChange,
10352
+ onOpenChange: handleOpenChange,
10353
+ // v2 `closeOnSelect` → `shouldCloseOnSelect` de react-stately, que ya trae el mismo default
10354
+ // por modo (`useSelectState.mjs:23`: cierra en `single`, se queda abierto en `multiple`).
10355
+ // `undefined` deja ese default intacto.
10356
+ ...(closeOnSelect === undefined
10357
+ ? {}
10358
+ : { shouldCloseOnSelect: closeOnSelect }),
10359
+ onClear,
9199
10360
  fullWidth,
9200
10361
  // `cn`, no una concatenación: el `className` del consumidor tiene que GANAR a las clases
9201
10362
  // de la fachada, y con una cadena suelta llegan las dos al DOM y decide el orden en que
@@ -9208,10 +10369,37 @@ fullWidth = true, isOpen, defaultOpen, onOpenChange, className, classNames, tran
9208
10369
  className: react.cn(FIELD_STACK_CLASS$1[size],
9209
10370
  // `w-full` y `min-w-40` son las dos ramas EXCLUYENTES de `fullWidth` en el `tv` de v2
9210
10371
  // (ver `ROOT_MIN_WIDTH_CLASS`).
9211
- fullWidth ? "w-full" : ROOT_MIN_WIDTH_CLASS, className),
10372
+ fullWidth ? "w-full" : ROOT_MIN_WIDTH_CLASS,
10373
+ // Grupo con nombre para el borde de foco del disparador `Group` (ver
10374
+ // `FOCUS_BORDER_GROUP_CLASS`). Sin la raíz `Autocomplete` no hace falta.
10375
+ usesAutocompleteShell ? "group/aura-select" : "",
10376
+ // v2 `classNames.base`: la raíz, igual que `className`, y por eso delante de él (en v2
10377
+ // los dos acababan en `slots.base({ class: cn(classNames?.base, className) })`).
10378
+ classNames?.base, className),
9212
10379
  };
9213
10380
  if (selectionMode === "multiple") {
9214
- return (jsxRuntime.jsx(react.Select, { ...commonProps, selectionMode: "multiple", value: toArray(selectedKeys), defaultValue: toArray(defaultSelectedKeys), onChange: (keys) => onSelectionChange?.(new Set(keys)), children: inner }));
10381
+ // `disallowEmptySelection` en `multiple`: react-stately lo fuerza a `false` en este modo
10382
+ // (`useSelectState.mjs:61`, `disallowEmptySelection: selectionMode === 'single'`, pisando
10383
+ // la prop), así que la guarda la pone la fachada: el cambio que dejaría la selección vacía
10384
+ // no se propaga. Controlado basta con no avisar; sin controlar, la fachada lleva el valor.
10385
+ const ownsValue = disallowEmptySelection && selectedKeys === undefined;
10386
+ const multipleProps = {
10387
+ ...commonProps,
10388
+ selectionMode: "multiple",
10389
+ value: ownsValue ? ownMultiple : toArray(selectedKeys),
10390
+ defaultValue: ownsValue ? undefined : toArray(defaultSelectedKeys),
10391
+ onChange: (keys) => {
10392
+ if (disallowEmptySelection && keys.length === 0) {
10393
+ return;
10394
+ }
10395
+ if (ownsValue) {
10396
+ setOwnMultiple(keys);
10397
+ }
10398
+ onSelectionChange?.(new Set(keys));
10399
+ onChange?.({ target: { name, value: keys.join(",") } });
10400
+ },
10401
+ };
10402
+ return usesAutocompleteShell ? (jsxRuntime.jsx(react.Autocomplete, { ...multipleProps, children: inner })) : (jsxRuntime.jsx(react.Select, { ...multipleProps, children: inner }));
9215
10403
  }
9216
10404
  // `Array.from` cubre las tres formas de `SelectSelection` sin ramas nuevas: `"all"` sigue
9217
10405
  // cayendo a `null` (v3 no admite "todo" en selección única) y un array se lee igual que un
@@ -9226,7 +10414,17 @@ fullWidth = true, isOpen, defaultOpen, onOpenChange, className, classNames, tran
9226
10414
  : defaultSelectedKeys === "all"
9227
10415
  ? null
9228
10416
  : (Array.from(defaultSelectedKeys)[0] ?? null);
9229
- return (jsxRuntime.jsx(react.Select, { ...commonProps, value: singleValue, defaultValue: singleDefault, onChange: (key) => onSelectionChange?.(key == null ? new Set() : new Set([key])), children: inner }));
10417
+ const singleProps = {
10418
+ ...commonProps,
10419
+ value: singleValue,
10420
+ defaultValue: singleDefault,
10421
+ onChange: (key) => {
10422
+ onSelectionChange?.(key == null ? new Set() : new Set([key]));
10423
+ // v2 llamaba a `onChange` después de `onSelectionChange` (ver `SelectChangeEvent`).
10424
+ onChange?.({ target: { name, value: key == null ? "" : String(key) } });
10425
+ },
10426
+ };
10427
+ return usesAutocompleteShell ? (jsxRuntime.jsx(react.Autocomplete, { ...singleProps, children: inner })) : (jsxRuntime.jsx(react.Select, { ...singleProps, children: inner }));
9230
10428
  };
9231
10429
 
9232
10430
  const defaultTranslations$4 = {
@@ -9345,40 +10543,472 @@ const StatTile = ({ title, value, icon, trend, translations = {}, className, pad
9345
10543
  .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 }))] })] })] }));
9346
10544
  };
9347
10545
 
9348
- /**
9349
- * Piezas que v3 monta FUERA del contenedor con scroll, como hermanas del root: el footer se queda
9350
- * a ancho completo (comentario de los autores en `table.css:15-18`), no se desplaza con la tabla.
9351
- */
9352
- const OUTSIDE_SCROLL = new Set([react.Table.Footer]);
9353
- /**
9354
- * Piezas que YA son un contenedor con scroll propio (`.table__scroll-container` /
9355
- * `.table__resizable-container`, ambas con `overflow` en `table.css`): si el consumidor monta una,
9356
- * la fachada no envuelve nada — anidar dos contenedores de scroll dejaría el interior sin ancho
9357
- * que desbordar.
9358
- */
9359
- const OWN_SCROLL = new Set([
9360
- react.Table.ScrollContainer,
9361
- react.Table.ResizableContainer,
9362
- ]);
9363
- /** Identidad por `child.type` contra los objetos reales de HeroUI (mismo patrón que la fachada `Alert`). */
9364
- function isPart$2(node, parts) {
9365
- return $8WNJc$react.isValidElement(node) && parts.has(node.type);
9366
- }
9367
- const TableFacade = ({ children, ...props }) => {
9368
- const nodes = $8WNJc$react.Children.toArray(children);
10546
+ // TECH-2459 — Fachada `Checkbox` (hallazgo loginos 07-sep: `index.tsx` reexportaba el `Checkbox`
10547
+ // CRUDO de v3, así que el JSX v2 de las apps — `isSelected` + `onValueChange` dentro de `Controller`
10548
+ // de RHF en backoffice ×12, smbs ×23, agency ×5 — no compilaba pese a existir el símbolo).
10549
+ //
10550
+ // HeroUI v3 convierte el Checkbox en compound (`Checkbox.Content` > `Checkbox.Control` >
10551
+ // `Checkbox.Indicator` + la etiqueta como children de `Content`), renombra `onValueChange` →
10552
+ // `onChange` y ELIMINA `color`, `size`, `radius`, `lineThrough`, `icon`, `classNames` y
10553
+ // `disableAnimation` (guía oficial `checkbox`). Su único eje propio es `variant`
10554
+ // (`primary`/`secondary`), que NO es un color: `secondary` es un tratamiento gris de menor énfasis
10555
+ // — el falso amigo de siempre (skill `auraui-design-tokens`).
10556
+ //
10557
+ // Esta fachada compone el compound por dentro y repone cada prop v2 con los literales del `tv`
10558
+ // de v2 (`@heroui/theme@2.4.15` → `dist/components/checkbox.js`), traducidos a utilidades de
10559
+ // Tailwind 4 sobre los tokens de Aurora. El relleno del estado marcado en v3 es el `::before` del
10560
+ // control (`bg-accent`, = `--color-primary` vía F1), así que `color` se reimplementa pisando ese
10561
+ // pseudo-elemento con `before:bg-<color>` — utilidad (capa `utilities`) que gana a la regla de
10562
+ // `checkbox.css` (capa `components`), incluida la de hover (`bg-accent-hover`).
10563
+ //
10564
+ // ⚠️ Estados de react-aria, verificados en el navegador contra `checkbox.css`: `data-selected`/
10565
+ // `data-indeterminate`/`data-invalid`/`data-disabled` viven en la RAÍZ (`.checkbox`);
10566
+ // `data-hovered`/`data-pressed`/`data-focus-visible` en `[data-slot="checkbox-content"]`. Por
10567
+ // eso hay dos grupos con nombre (`group/checkbox`, `group/content`) y NUNCA `data-[hover=true]`
10568
+ // (trampa 3 de la skill: v2 escribía `data-hover`; react-aria escribe `data-hovered`).
10569
+ /**
10570
+ * Relleno del estado marcado (`::before` del control) y del indeterminado (fondo del control)
10571
+ * por color. `primary` no pisa nada: el `bg-accent` de v3 YA es `--color-primary`. `default`
10572
+ * usa el gris de Aurora (`--color-default`, = v2), nunca el `--default` crudo de v3 (D-F1-3).
10573
+ *
10574
+ * Literal v2: `after:bg-<color> after:text-<color>-foreground text-<color>-foreground`.
10575
+ */
10576
+ const CONTROL_COLOR = {
10577
+ primary: "",
10578
+ default: "before:bg-default group-data-[indeterminate=true]/checkbox:bg-default",
10579
+ secondary: "before:bg-secondary group-data-[indeterminate=true]/checkbox:bg-secondary",
10580
+ success: "before:bg-success group-data-[indeterminate=true]/checkbox:bg-success",
10581
+ warning: "before:bg-warning group-data-[indeterminate=true]/checkbox:bg-warning",
10582
+ danger: "before:bg-danger group-data-[indeterminate=true]/checkbox:bg-danger",
10583
+ };
10584
+ /**
10585
+ * Color del glifo (check / línea). v3 lo fija en el SVG (`[data-slot="checkbox-default-indicator--
10586
+ * checkmark"] { text-accent-foreground }`), así que hay que alcanzar al SVG, no al indicador.
10587
+ */
10588
+ const INDICATOR_COLOR = {
10589
+ primary: "",
10590
+ default: "[&_svg]:text-default-foreground",
10591
+ secondary: "[&_svg]:text-secondary-foreground",
10592
+ success: "[&_svg]:text-success-foreground",
10593
+ warning: "[&_svg]:text-warning-foreground",
10594
+ danger: "[&_svg]:text-danger-foreground",
10595
+ };
10596
+ // v2 (`checkbox.js`, variante `size`): wrapper `w-4/w-5/w-6` (16/20/24px), label
10597
+ // `text-small/medium/large`, icon `w-3 h-2` / `w-4 h-3` / `w-5 h-4`. v3: control `size-4`, glifo
10598
+ // `size-2.5`, texto `text-sm`, para todos.
10599
+ const CONTROL_SIZE = {
10600
+ sm: "size-4",
10601
+ md: "size-5",
10602
+ lg: "size-6",
10603
+ };
10604
+ const INDICATOR_SIZE = {
10605
+ sm: "size-3 [&_svg]:size-2.5",
10606
+ md: "size-3.5 [&_svg]:size-3",
10607
+ lg: "size-4 [&_svg]:size-3.5",
10608
+ };
10609
+ const LABEL_SIZE = {
10610
+ sm: "text-sm",
10611
+ md: "text-base",
10612
+ lg: "text-lg",
10613
+ };
10614
+ // v2: `rounded-[calc(theme(borderRadius.medium)*K)]` con `medium` = 12px → 6 / 7.2 / 8.4 px, y por
10615
+ // defecto el radio lo decidía `size` (sm→0.5, md→0.6, lg→0.7). El `::before` (relleno) lleva el
10616
+ // mismo radio porque aquí cubre el border-box completo (ver `CONTROL_BASE`).
10617
+ const RADIUS_CLASS$3 = {
10618
+ none: "rounded-none before:rounded-none",
10619
+ sm: "rounded-[6px] before:rounded-[6px]",
10620
+ md: "rounded-[7.2px] before:rounded-[7.2px]",
10621
+ lg: "rounded-[8.4px] before:rounded-[8.4px]",
10622
+ full: "rounded-full before:rounded-full",
10623
+ };
10624
+ const RADIUS_BY_SIZE = {
10625
+ sm: "sm",
10626
+ md: "md",
10627
+ lg: "lg",
10628
+ };
10629
+ /**
10630
+ * Caja del control (slot `wrapper` de v2): borde de 2px en `--color-default` sin sombra
10631
+ * (`before:border-2 before:border-default`), y el relleno cubriendo la caja ENTERA al marcarse.
10632
+ *
10633
+ * En v3 el borde vive en el propio control (1px, `border-field-border`, `shadow-field`) y el
10634
+ * relleno es un `::before` a `inset-0`, que solo alcanza al padding-box: por eso v3 pone
10635
+ * `border-transparent` al marcarse y el relleno queda 1px por dentro. Con el borde de 2px de v2
10636
+ * eso dejaría un anillo de 2px: se lleva el `::before` al border-box (`-inset-0.5` = −2px) y se
10637
+ * quita el `overflow-hidden` de v3 para que no lo recorte. El `::before` es un descendiente
10638
+ * posicionado, así que pinta ENCIMA del borde y lo tapa, como el `after` de v2.
10639
+ *
10640
+ * Hover v2: `group-data-[hover=true]:before:bg-default-100` — tinte suave de la caja sin marcar
10641
+ * (en v3 la caja es el fondo del control, no un pseudo). `data-hovered` lo emite `Checkbox.Content`.
10642
+ *
10643
+ * Indeterminado: v3 pinta el fondo del control (dentro del borde). Se enciende también el
10644
+ * `::before` para que el relleno cubra la caja entera, igual que al marcar.
10645
+ */
10646
+ const CONTROL_BASE = [
10647
+ "border-2 border-default shadow-none overflow-visible",
10648
+ "before:-inset-0.5",
10649
+ "group-data-[hovered=true]/content:bg-default-100",
10650
+ "group-data-[indeterminate=true]/checkbox:before:scale-100",
10651
+ "group-data-[indeterminate=true]/checkbox:before:opacity-100",
10652
+ ].join(" ");
10653
+ // v2 (`lineThrough: true`, slot `label`): la raya crece de 0 al 100% al marcarse y la etiqueta se
10654
+ // atenúa al 60%. `data-selected` está en la raíz → `group-data-[…]/checkbox`.
10655
+ const LINE_THROUGH = [
10656
+ "inline-flex items-center justify-center",
10657
+ "before:absolute before:h-0.5 before:w-0 before:bg-foreground before:content-['']",
10658
+ "group-data-[selected=true]/checkbox:opacity-60",
10659
+ "group-data-[selected=true]/checkbox:before:w-full",
10660
+ ].join(" ");
10661
+ // Los subcomponentes compound de v3, adjuntos como estáticos para que la API v3 siga funcionando
10662
+ // a través de la fachada (`<Checkbox><Checkbox.Control>…`).
10663
+ const COMPOUND_PARTS$1 = new Set([
10664
+ react.Checkbox.Control,
10665
+ react.Checkbox.Indicator,
10666
+ react.Checkbox.Content,
10667
+ ]);
10668
+ /** API compound de v3 detectada por hijos directos: la fachada no envuelve nada. */
10669
+ function hasCompoundChildren$1(children) {
10670
+ return $8WNJc$react.Children.toArray(children).some((child) => $8WNJc$react.isValidElement(child) && COMPOUND_PARTS$1.has(child.type));
10671
+ }
10672
+ /**
10673
+ * Checkbox - Casilla basada en HeroUI v3 con la API pública de v2 intacta (TECH-2459).
10674
+ *
10675
+ * - **API v2**: `children` como etiqueta, `isSelected`/`defaultSelected` + `onValueChange`
10676
+ * (también `onChange`), `color`, `size`, `radius`, `lineThrough`, `icon`, `classNames`. Funciona
10677
+ * dentro de `Controller` de react-hook-form (`isSelected={value} onValueChange={onChange}`).
10678
+ * - **API compound v3**: si los children traen `Checkbox.Control`/`Indicator`/`Content`, pasa tal
10679
+ * cual a la raíz de HeroUI (con `onValueChange` seguido igualmente).
10680
+ */
10681
+ const CheckboxFacade = ({ children, onChange, onValueChange, color = "primary", size = "md", radius, lineThrough = false, icon, disableAnimation = false, isInvalid, classNames, className, ...rest }) => {
10682
+ const handleChange = (isSelected) => {
10683
+ onChange?.(isSelected);
10684
+ onValueChange?.(isSelected);
10685
+ };
10686
+ if (hasCompoundChildren$1(children)) {
10687
+ return (jsxRuntime.jsx(react.Checkbox, { ...rest, isInvalid: isInvalid, onChange: handleChange, className: react.cn(classNames?.base, className), children: children }));
10688
+ }
10689
+ const indicatorContent = typeof icon === "function"
10690
+ ? (state) => icon({
10691
+ isSelected: state.isSelected,
10692
+ isIndeterminate: state.isIndeterminate,
10693
+ isDisabled: state.isDisabled,
10694
+ })
10695
+ : icon;
10696
+ const hasLabel = children !== undefined && children !== null && children !== false;
10697
+ return (jsxRuntime.jsx(react.Checkbox, { ...rest, isInvalid: isInvalid, onChange: handleChange,
10698
+ // Base v2: `group relative max-w-fit inline-flex items-center justify-start cursor-pointer
10699
+ // p-2 -m-2 select-none` (el `p-2 -m-2` es el área de pulsado ampliada de v2). v3
10700
+ // (`.checkbox`) usa `flex flex-col items-start` para apilar `Description`/`FieldError`,
10701
+ // slots que esta fachada no renderiza; en **inline** la caja participa de la línea de
10702
+ // texto, donde la esperan los consumidores (mismo criterio que la fachada `Switch`).
10703
+ 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],
10704
+ // v2 `isInvalid`: `before:border-danger` (el borde vivía en el pseudo).
10705
+ 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 && (
10706
+ // Slot `label` de v2: `relative text-foreground select-none` + tamaño de texto.
10707
+ // `data-slot="label"` para que las reglas de cursor/selección de `.checkbox` de v3
10708
+ // le alcancen igual que a un `<Label>`.
10709
+ 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 }))] }) }));
10710
+ };
10711
+ CheckboxFacade.displayName = "Aurora.Checkbox";
10712
+ /**
10713
+ * `Checkbox` de Aurora: fachada v2 + estáticos compound de v3 (`Checkbox.Control`,
10714
+ * `Checkbox.Indicator`, `Checkbox.Content`), los mismos objetos que exporta `@heroui/react`.
10715
+ */
10716
+ const Checkbox = Object.assign(CheckboxFacade, {
10717
+ Control: react.Checkbox.Control,
10718
+ Indicator: react.Checkbox.Indicator,
10719
+ Content: react.Checkbox.Content,
10720
+ });
10721
+
10722
+ // TECH-2459 — Piezas que la fachada `Table.Content` inserta para reponer la API v2 que v3 dejó en
10723
+ // manos de la app (defectos reportados por beweossmbs, 23-sep). Privadas: NO se reexportan desde
10724
+ // `_internal/index.ts` ni desde el `index.ts` de la carpeta.
10725
+ //
10726
+ // Todo se decide por IDENTIDAD de `child.type` contra los objetos reales de HeroUI (mismo patrón
10727
+ // que las fachadas `Alert`, `Tabs` y el propio root de `Table`), nunca por `displayName`.
10728
+ /**
10729
+ * `isHeaderSticky` declarado en el ROOT (`<Table isHeaderSticky>`, que es donde lo ponía v2) viaja
10730
+ * hasta `Table.Content` por contexto: el `<thead>` lo pinta el contenido, no el root.
10731
+ */
10732
+ const TableStickyContext = $8WNJc$react.createContext(undefined);
10733
+ /**
10734
+ * Clases de v2 para `isHeaderSticky` (`@heroui/theme@2.4.15` → `dist/components/table.js`,
10735
+ * variante `isHeaderSticky.true.thead`): `sticky top-0 z-20 [&>tr]:first:shadow-small`. La sombra
10736
+ * se descarta: `shadow-small` es una clase del plugin de v2 que en Tailwind 4 no genera nada, y
10737
+ * v3 separa la cabecera del cuerpo con su propio borde (`.table__header { border-b }`).
10738
+ *
10739
+ * El «contenedor con scroll» respecto al que se pega es el `Table.ScrollContainer` que inserta la
10740
+ * fachada del root (`overflow-x: auto` ⇒ `overflow-y` computa `auto`), exactamente como en v2 lo
10741
+ * era el `wrapper` (`overflow-auto`): la cabecera se queda fija cuando ese contenedor tiene alto
10742
+ * acotado y desplaza en vertical.
10743
+ */
10744
+ const STICKY_HEADER_CLASS = "sticky top-0 z-20";
10745
+ /** Clave fija de la columna/celda de selección insertadas (no colisiona con claves de negocio). */
10746
+ const SELECTION_KEY = "__aurora-selection";
10747
+ /**
10748
+ * Columna de «seleccionar todo». El `Checkbox slot="selection"` recibe de react-aria, por
10749
+ * `CheckboxContext`, el estado de la tabla: marcado con todo seleccionado, INDETERMINADO con una
10750
+ * selección parcial, y su `onChange` selecciona/deselecciona todo — `onSelectionChange` recibe
10751
+ * `"all"`, igual que en v2 (`useTableSelectAllCheckbox`). La etiqueta accesible también la pone
10752
+ * react-aria, traducida a la locale activa: por eso esta pieza no tiene texto propio ni
10753
+ * `translations`.
10754
+ *
10755
+ * `pe-0` es lo que hace la anatomía oficial de v3 con su columna de selección: la casilla no
10756
+ * necesita el padding final de una columna de texto.
10757
+ */
10758
+ function SelectionColumn() {
10759
+ return (jsxRuntime.jsx(react.Table.Column, { className: "pe-0", id: SELECTION_KEY, children: jsxRuntime.jsx(Checkbox, { slot: "selection" }) }));
10760
+ }
10761
+ /** Casilla por fila (`useTableSelectionCheckbox` vía `CheckboxContext`, como en v2). */
10762
+ function SelectionCell() {
10763
+ return (jsxRuntime.jsx(react.Table.Cell, { className: "pe-0", children: jsxRuntime.jsx(Checkbox, { slot: "selection" }) }));
10764
+ }
10765
+ function isElementOf(node, type) {
10766
+ return $8WNJc$react.isValidElement(node) && node.type === type;
10767
+ }
10768
+ /**
10769
+ * Hijos listos para pasarse como argumentos VARIÁDICOS de `cloneElement`/`createElement`. Un array
10770
+ * pasado como argumento único es para React una LISTA y exige `key` en cada elemento: si la app
10771
+ * escribió hijos JSX sueltos (sin clave, legal) y la fachada los reenviara como un solo array,
10772
+ * saltaría «Each child in a list should have a unique key» (medido en jsdom). Esparcidos, React
10773
+ * los trata como los trató al escribirlos.
10774
+ */
10775
+ function spread(node) {
10776
+ return Array.isArray(node) ? node : [node];
10777
+ }
10778
+ /**
10779
+ * Aplica `fn` a cada nodo de un árbol de `children` atravesando arrays y Fragments, sin tocar
10780
+ * claves (`cloneElement` las conserva). `Children.map` no sirve aquí: re-prefija las claves y NO
10781
+ * entra en Fragments — y las apps envuelven en Fragment cuando el contenido es condicional.
10782
+ */
10783
+ function mapNodes(node, fn) {
10784
+ if (Array.isArray(node)) {
10785
+ return node.map((child) => mapNodes(child, fn));
10786
+ }
10787
+ if (isElementOf(node, $8WNJc$react.Fragment)) {
10788
+ return $8WNJc$react.cloneElement(node, undefined, ...spread(mapNodes(node.props.children, fn)));
10789
+ }
10790
+ return fn(node);
10791
+ }
10792
+ /**
10793
+ * ¿Hay ya una casilla de selección montada (por la fachada o por la app) en este subárbol
10794
+ * estático? Mira la identidad de las piezas propias y cualquier elemento con `slot="selection"`
10795
+ * (la forma que documenta v3). Las funciones de render no se pueden inspeccionar sin llamarlas:
10796
+ * esas cuentan como «no hay».
10797
+ */
10798
+ function hasSelectionControl(node) {
10799
+ if (Array.isArray(node)) {
10800
+ return node.some(hasSelectionControl);
10801
+ }
10802
+ if (!$8WNJc$react.isValidElement(node)) {
10803
+ return false;
10804
+ }
10805
+ if (node.type === SelectionColumn || node.type === SelectionCell) {
10806
+ return true;
10807
+ }
10808
+ const props = node.props;
10809
+ if (props.slot === "selection") {
10810
+ return true;
10811
+ }
10812
+ return typeof props.children === "function"
10813
+ ? false
10814
+ : hasSelectionControl(props.children);
10815
+ }
10816
+ function containsType(node, type) {
10817
+ if (Array.isArray(node)) {
10818
+ return node.some((child) => containsType(child, type));
10819
+ }
10820
+ if (!$8WNJc$react.isValidElement(node)) {
10821
+ return false;
10822
+ }
10823
+ if (node.type === type) {
10824
+ return true;
10825
+ }
10826
+ const children = node.props.children;
10827
+ return typeof children === "function" ? false : containsType(children, type);
10828
+ }
10829
+ /**
10830
+ * Columna con `allowsSorting`: v2 pintaba el chevron por su cuenta (`TableColumnHeader`,
10831
+ * `ChevronDownIcon` con `data-direction`); v3 no pinta nada y pide que la app monte
10832
+ * `Table.SortableColumnHeader` en el render prop de la columna. Aquí se monta esa pieza de v3
10833
+ * — aspecto de v3 (§9.1): chevron a la derecha que apunta arriba en ascendente y gira en
10834
+ * descendente, y sin chevron mientras la columna no es la ordenada.
10835
+ *
10836
+ * No se toca la columna si la app ya decide el contenido con un render prop (entonces es suyo) o
10837
+ * si ya montó un `SortableColumnHeader` estático.
10838
+ */
10839
+ function withSortIndicator(node) {
10840
+ if (!isElementOf(node, react.Table.Column)) {
10841
+ return node;
10842
+ }
10843
+ const { allowsSorting, children } = node.props;
10844
+ if (!allowsSorting ||
10845
+ typeof children === "function" ||
10846
+ containsType(children, react.Table.SortableColumnHeader)) {
10847
+ return node;
10848
+ }
10849
+ return $8WNJc$react.cloneElement(node, {
10850
+ children: ({ sortDirection }) => (jsxRuntime.jsx(react.Table.SortableColumnHeader, { sortDirection: sortDirection, children: children })),
10851
+ });
10852
+ }
10853
+ /** Compone una clase sobre un `className` de react-aria, que puede ser string o render prop. */
10854
+ function withClass(className, extra) {
10855
+ if (typeof className === "function") {
10856
+ return (renderProps) => react.cn(className(renderProps), extra);
10857
+ }
10858
+ return react.cn(className, extra);
10859
+ }
10860
+ /**
10861
+ * `Table.Header`: indicador de orden en cada columna ordenable, clase sticky si se pidió y la
10862
+ * columna de selección delante si toca.
10863
+ *
10864
+ * Con columnas dinámicas (`columns` + función) la columna de selección no se puede colar en el
10865
+ * array de la app — su función de render la recibiría como si fuera una columna de negocio. Se usa
10866
+ * la forma que documenta react-aria para mezclar estáticas y dinámicas: la estática delante y las
10867
+ * de la app en un `Table.Collection` con sus `items`. El `Collection` exterior del header hereda
10868
+ * `dependencies` por contexto (`CollectionBuilder`), así que no se pierden.
10869
+ */
10870
+ function transformHeader(header, options) {
10871
+ const { children, columns, className } = header.props;
10872
+ const props = options.sticky
10873
+ ? { className: withClass(className, STICKY_HEADER_CLASS) }
10874
+ : {};
10875
+ if (typeof children === "function") {
10876
+ const render = children;
10877
+ const wrapped = (column) => withSortIndicator(render(column));
10878
+ if (!options.insertSelection) {
10879
+ return $8WNJc$react.cloneElement(header, { ...props, children: wrapped });
10880
+ }
10881
+ return $8WNJc$react.cloneElement(header, { ...props, columns: undefined }, jsxRuntime.jsx(SelectionColumn, {}, SELECTION_KEY), jsxRuntime.jsx(react.Table.Collection, { items: columns, children: wrapped }, "columns"));
10882
+ }
10883
+ const mapped = spread(mapNodes(children, withSortIndicator));
10884
+ if (!options.insertSelection) {
10885
+ return $8WNJc$react.cloneElement(header, props, ...mapped);
10886
+ }
10887
+ return $8WNJc$react.cloneElement(header, props, jsxRuntime.jsx(SelectionColumn, {}, SELECTION_KEY), ...mapped);
10888
+ }
10889
+ /**
10890
+ * Casilla delante de las celdas de una fila. Solo sabe tratar `Table.Row` (el mismo objeto que el
10891
+ * `TableRow` suelto): una fila envuelta en un componente propio de la app no se puede abrir, y se
10892
+ * devuelve tal cual (límite documentado en el README).
10893
+ */
10894
+ function withSelectionCell(row) {
10895
+ if (!isElementOf(row, react.Table.Row)) {
10896
+ return row;
10897
+ }
10898
+ const { children, columns } = row.props;
10899
+ if (typeof children === "function") {
10900
+ // Mismo mecanismo que el header: estática + `Collection` con las columnas de la app. El
10901
+ // `idScope` de la fila (su `id`) lo hereda la colección interior por contexto, así que las
10902
+ // claves de las celdas siguen siendo únicas por fila.
10903
+ return $8WNJc$react.cloneElement(row, { columns: undefined }, jsxRuntime.jsx(SelectionCell, {}, SELECTION_KEY), jsxRuntime.jsx(react.Table.Collection, { items: columns, children: children }, "cells"));
10904
+ }
10905
+ if (hasSelectionControl(children)) {
10906
+ return row;
10907
+ }
10908
+ return $8WNJc$react.cloneElement(row, undefined, jsxRuntime.jsx(SelectionCell, {}, SELECTION_KEY), ...spread(children));
10909
+ }
10910
+ /** `Table.Body`: una casilla por fila, tanto con `items` + función como con filas estáticas. */
10911
+ function transformBody(body) {
10912
+ const { children } = body.props;
10913
+ if (typeof children === "function") {
10914
+ const render = children;
10915
+ return $8WNJc$react.cloneElement(body, {
10916
+ children: (item) => withSelectionCell(render(item)),
10917
+ });
10918
+ }
10919
+ return $8WNJc$react.cloneElement(body, undefined, ...spread(mapNodes(children, withSelectionCell)));
10920
+ }
10921
+
10922
+ /** Default de v2 (`use-table`, `@heroui/table@2.2.32`), sin reinterpretar. */
10923
+ function defaultShowCheckboxes(props) {
10924
+ const behavior = props.selectionBehavior ?? "toggle";
10925
+ return props.selectionMode === "multiple" && behavior !== "replace";
10926
+ }
10927
+ /** Primer `Table.Header` del árbol (atravesando arrays y Fragments), para decidir la selección. */
10928
+ function findHeader(node) {
10929
+ let found;
10930
+ mapNodes(node, (child) => {
10931
+ if (!found && isElementOf(child, react.Table.Header)) {
10932
+ found = child;
10933
+ }
10934
+ return child;
10935
+ });
10936
+ return found;
10937
+ }
10938
+ /**
10939
+ * `Table.Content` de Aurora: el `Table.Content` de HeroUI v3 con las casillas de selección, el
10940
+ * indicador de orden y la cabecera fija de v2. Idempotente: si la app ya montó sus casillas
10941
+ * (`slot="selection"`) o su `SortableColumnHeader`, no se inserta nada encima.
10942
+ */
10943
+ function TableContent({ children, isHeaderSticky, showSelectionCheckboxes, ...props }) {
10944
+ const rootSticky = $8WNJc$react.useContext(TableStickyContext);
10945
+ const sticky = isHeaderSticky ?? rootSticky ?? false;
10946
+ const header = findHeader(children);
10947
+ // La decisión es de TABLA, no de fila: si la cabecera ya trae casilla (la montó la app), no
10948
+ // se inserta ni en la cabecera ni en las filas — insertar solo en unas descuadraría el número
10949
+ // de celdas y react-aria lanza `Cell count must match column count`.
10950
+ const insertSelection = (showSelectionCheckboxes ?? defaultShowCheckboxes(props)) &&
10951
+ header !== undefined &&
10952
+ !(typeof header.props.children !== "function" &&
10953
+ hasSelectionControl(header.props.children));
10954
+ const content = mapNodes(children, (child) => {
10955
+ if (isElementOf(child, react.Table.Header)) {
10956
+ return transformHeader(child, { insertSelection, sticky });
10957
+ }
10958
+ if (insertSelection && isElementOf(child, react.Table.Body)) {
10959
+ return transformBody(child);
10960
+ }
10961
+ return child;
10962
+ });
10963
+ // Esparcido y no `{content}`: ver `spread` (un array como hijo único exigiría claves).
10964
+ return $8WNJc$react.createElement(react.Table.Content, props, ...spread(content));
10965
+ }
10966
+ TableContent.displayName = "Aurora.Table.Content";
10967
+
10968
+ /**
10969
+ * Piezas que v3 monta FUERA del contenedor con scroll, como hermanas del root: el footer se queda
10970
+ * a ancho completo (comentario de los autores en `table.css:15-18`), no se desplaza con la tabla.
10971
+ */
10972
+ const OUTSIDE_SCROLL = new Set([react.Table.Footer]);
10973
+ /**
10974
+ * Piezas que YA son un contenedor con scroll propio (`.table__scroll-container` /
10975
+ * `.table__resizable-container`, ambas con `overflow` en `table.css`): si el consumidor monta una,
10976
+ * la fachada no envuelve nada — anidar dos contenedores de scroll dejaría el interior sin ancho
10977
+ * que desbordar.
10978
+ */
10979
+ const OWN_SCROLL = new Set([
10980
+ react.Table.ScrollContainer,
10981
+ react.Table.ResizableContainer,
10982
+ ]);
10983
+ /** Identidad por `child.type` contra los objetos reales de HeroUI (mismo patrón que la fachada `Alert`). */
10984
+ function isPart$2(node, parts) {
10985
+ return $8WNJc$react.isValidElement(node) && parts.has(node.type);
10986
+ }
10987
+ /**
10988
+ * `isHeaderSticky` del root viaja a `Table.Content` por contexto (el `<thead>` lo pinta el
10989
+ * contenido). Solo se monta el provider cuando la app declara la prop: sin ella el árbol del root
10990
+ * queda exactamente como antes (`.Provider` y no `<Context value>` porque el peer admite React 18).
10991
+ */
10992
+ function withSticky(isHeaderSticky, root) {
10993
+ return isHeaderSticky === undefined ? (root) : (jsxRuntime.jsx(TableStickyContext.Provider, { value: isHeaderSticky, children: root }));
10994
+ }
10995
+ const TableFacade = ({ children, isHeaderSticky, ...props }) => {
10996
+ const nodes = $8WNJc$react.Children.toArray(children);
9369
10997
  // Sin hijos, o con un contenedor de scroll ya montado a mano: el root tal cual.
9370
10998
  if (nodes.length === 0 || nodes.some((node) => isPart$2(node, OWN_SCROLL))) {
9371
- return jsxRuntime.jsx(react.Table, { ...props, children: children });
10999
+ return withSticky(isHeaderSticky, jsxRuntime.jsx(react.Table, { ...props, children: children }));
9372
11000
  }
9373
11001
  const scrollable = nodes.filter((node) => !isPart$2(node, OUTSIDE_SCROLL));
9374
11002
  const outsideScroll = nodes.filter((node) => isPart$2(node, OUTSIDE_SCROLL));
9375
- return (jsxRuntime.jsxs(react.Table, { ...props, children: [jsxRuntime.jsx(react.Table.ScrollContainer, { children: scrollable }), outsideScroll] }));
11003
+ return withSticky(isHeaderSticky, jsxRuntime.jsxs(react.Table, { ...props, children: [jsxRuntime.jsx(react.Table.ScrollContainer, { children: scrollable }), outsideScroll] }));
9376
11004
  };
9377
11005
  TableFacade.displayName = "Aurora.Table";
9378
11006
  /**
9379
11007
  * `Table` de Aurora: el root de HeroUI v3 con el `ScrollContainer` que faltaba, más los estáticos
9380
- * del compound de v3 — los MISMOS objetos que exporta `@heroui/react`, así que
9381
- * `Table.Content`/`Table.Header`/`isRowHeader`… se comportan igual que antes.
11008
+ * del compound de v3 — los MISMOS objetos que exporta `@heroui/react` (así que
11009
+ * `Table.Header`/`Table.Column isRowHeader`… se comportan igual que antes), SALVO `Table.Content`,
11010
+ * que es la fachada de Aurora: inserta las casillas de selección múltiple, el indicador de orden
11011
+ * de `allowsSorting` y la cabecera fija de `isHeaderSticky` (ver `TableContent.tsx`).
9382
11012
  *
9383
11013
  * `Table.Root` queda deliberadamente crudo (el root pelado de v3, sin scroll): es la vía de escape
9384
11014
  * para quien quiera montar la anatomía entera a mano.
@@ -9389,7 +11019,7 @@ const Table = Object.assign(TableFacade, {
9389
11019
  Collection: react.Table.Collection,
9390
11020
  Column: react.Table.Column,
9391
11021
  ColumnResizer: react.Table.ColumnResizer,
9392
- Content: react.Table.Content,
11022
+ Content: TableContent,
9393
11023
  Footer: react.Table.Footer,
9394
11024
  Header: react.Table.Header,
9395
11025
  LoadMore: react.Table.LoadMore,
@@ -9458,6 +11088,225 @@ function AuraTable({ columns, items, renderCell, children, className, ...props }
9458
11088
  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)) })] }) }));
9459
11089
  }
9460
11090
 
11091
+ // ── Autocrecimiento entre `minRows` y `maxRows` (reposición de v2) ─────────────────────────────
11092
+ //
11093
+ // v2 montaba `react-textarea-autosize@8.5.9` (`@heroui/input@2.4.22`, `dist/chunk-RIE4DNUQ.mjs`)
11094
+ // y aquí esa dependencia no existe, así que se reimplementa su algoritmo, leído en su compilado
11095
+ // (`dist/react-textarea-autosize.browser.esm.js`, `calculateNodeHeight` + `resizeTextarea`):
11096
+ //
11097
+ // 1. Se copia el estilo que decide el ajuste de línea (fuente, relleno, borde, ancho…) a un
11098
+ // `<textarea>` OCULTO compartido, se le pone el valor (o el placeholder, o "x") y se lee su
11099
+ // `scrollHeight`. Se mide en un clon y no en el nodo real porque encoger el real a 0 para
11100
+ // medirlo mueve el scroll de la página cuando el campo está cerca del borde inferior.
11101
+ // 2. El alto de UNA línea sale del mismo clon con el valor "x", no de una constante: así cuadra
11102
+ // con la fuente y el tamaño que el campo tenga de verdad.
11103
+ // 3. `alto = min(máx, max(mín, contenido))`, con mín/máx = filas × línea + relleno (+ borde en
11104
+ // `border-box`). Si `maxRows < minRows`, gana `maxRows` — el `min` va por fuera, como en v2.
11105
+ // 4. Se escribe con `setProperty("height", …, "important")`, literal de v2.
11106
+ //
11107
+ // Cuándo se recalcula (los mismos disparadores que v2, más el ancho):
11108
+ // · en cada render de la fachada (layout effect sin dependencias, como v2) → cubre el valor
11109
+ // CONTROLADO y cualquier cambio de `size`/`className` que mueva la fuente o el ancho;
11110
+ // · al escribir, en los dos modos (`resize` desde el `onChange` de la fachada). Hace falta
11111
+ // aunque el valor viva en estado de React: en modo NO controlado ese estado es del
11112
+ // `TextField` de react-aria, y su re-render no re-renderiza la fachada, así que el layout
11113
+ // effect de arriba no corre;
11114
+ // · al cambiar el ANCHO del campo (`ResizeObserver`; v2 solo escuchaba `window.resize`, que no
11115
+ // ve un panel que se estrecha) — solo si cambió el ancho, para no reaccionar a nuestro propio
11116
+ // cambio de alto;
11117
+ // · al terminar de cargar una fuente web (`document.fonts` → `loadingdone`, como v2);
11118
+ // · tras el `reset` del formulario (como v2): `useFormReset` de react-aria devuelve el estado del
11119
+ // `TextField` al `defaultValue`, otra vez sin re-renderizar la fachada. Se mide en el frame
11120
+ // siguiente, cuando React ya ha escrito el valor restaurado en el DOM.
11121
+ //
11122
+ // Scroll: v2 ocultaba la barra mientras no se alcanzaba el tope (`data-hide-scroll` →
11123
+ // `scrollbar-hide`, `@heroui/theme@2.4.15`); aquí se consigue lo mismo con `overflow-y: hidden`
11124
+ // inline hasta que el contenido supera el máximo, y a partir de ahí se deja el scroll nativo.
11125
+ const HIDDEN_TEXTAREA_STYLE = {
11126
+ "min-height": "0",
11127
+ "max-height": "none",
11128
+ height: "0",
11129
+ visibility: "hidden",
11130
+ overflow: "hidden",
11131
+ position: "absolute",
11132
+ "z-index": "-1000",
11133
+ top: "0",
11134
+ right: "0",
11135
+ display: "block",
11136
+ };
11137
+ // Lista literal de `react-textarea-autosize` (`SIZING_STYLE`).
11138
+ const SIZING_STYLE = [
11139
+ "borderBottomWidth",
11140
+ "borderLeftWidth",
11141
+ "borderRightWidth",
11142
+ "borderTopWidth",
11143
+ "boxSizing",
11144
+ "fontFamily",
11145
+ "fontSize",
11146
+ "fontStyle",
11147
+ "fontWeight",
11148
+ "letterSpacing",
11149
+ "lineHeight",
11150
+ "paddingBottom",
11151
+ "paddingLeft",
11152
+ "paddingRight",
11153
+ "paddingTop",
11154
+ "tabSize",
11155
+ "textIndent",
11156
+ "textRendering",
11157
+ "textTransform",
11158
+ "width",
11159
+ "wordBreak",
11160
+ "wordSpacing",
11161
+ "scrollbarGutter",
11162
+ ];
11163
+ let hiddenTextarea = null;
11164
+ const forceHiddenStyles = (node) => {
11165
+ for (const key of Object.keys(HIDDEN_TEXTAREA_STYLE)) {
11166
+ node.style.setProperty(key, HIDDEN_TEXTAREA_STYLE[key], "important");
11167
+ }
11168
+ };
11169
+ const px = (value) => Number.parseFloat(value) || 0;
11170
+ /**
11171
+ * Mide el alto que necesita `node` para su contenido y lo acota entre `minRows` y `maxRows`.
11172
+ * Devuelve `null` si el nodo está fuera del DOM (no hay estilo computado que leer).
11173
+ */
11174
+ function measureTextareaHeight(node, minRows, maxRows) {
11175
+ // v2 detectaba el nodo desconectado por un `boxSizing` vacío; aquí se pregunta directamente,
11176
+ // y un `boxSizing` vacío en un nodo conectado se trata como `content-box` (el valor inicial).
11177
+ if (!node.isConnected) {
11178
+ return null;
11179
+ }
11180
+ const computed = window.getComputedStyle(node);
11181
+ const isBorderBox = computed.boxSizing === "border-box";
11182
+ const paddingSize = px(computed.paddingTop) + px(computed.paddingBottom);
11183
+ const borderSize = px(computed.borderTopWidth) + px(computed.borderBottomWidth);
11184
+ if (!hiddenTextarea) {
11185
+ hiddenTextarea = document.createElement("textarea");
11186
+ hiddenTextarea.setAttribute("tabindex", "-1");
11187
+ hiddenTextarea.setAttribute("aria-hidden", "true");
11188
+ }
11189
+ if (hiddenTextarea.parentNode === null) {
11190
+ document.body.appendChild(hiddenTextarea);
11191
+ }
11192
+ const clone = hiddenTextarea;
11193
+ const cloneStyle = clone.style;
11194
+ for (const key of SIZING_STYLE) {
11195
+ cloneStyle[key] =
11196
+ computed[key] ?? "";
11197
+ }
11198
+ forceHiddenStyles(clone);
11199
+ const contentOf = () => isBorderBox
11200
+ ? clone.scrollHeight + borderSize
11201
+ : clone.scrollHeight - paddingSize;
11202
+ const text = node.value || node.placeholder || "x";
11203
+ clone.value = text;
11204
+ let content = contentOf();
11205
+ // Doble asignación y medida: bug de Firefox que v2 también esquiva (bugzilla 1795904).
11206
+ clone.value = text;
11207
+ content = contentOf();
11208
+ clone.value = "x";
11209
+ const rowHeight = clone.scrollHeight - paddingSize;
11210
+ const extra = isBorderBox ? paddingSize + borderSize : 0;
11211
+ const minHeight = rowHeight * minRows + extra;
11212
+ const maxHeight = rowHeight * maxRows + extra;
11213
+ return {
11214
+ height: Math.min(maxHeight, Math.max(minHeight, content)),
11215
+ rowHeight,
11216
+ overflows: content > maxHeight,
11217
+ };
11218
+ }
11219
+ // `useLayoutEffect` en el servidor avisa en React 18; el alto solo tiene sentido en el cliente.
11220
+ const useIsomorphicLayoutEffect = typeof window !== "undefined" ? $8WNJc$react.useLayoutEffect : $8WNJc$react.useEffect;
11221
+ /**
11222
+ * Autocrecimiento del `<textarea>` apuntado por `nodeRef`. Devuelve el `resize` para llamarlo
11223
+ * desde el `onChange` de la fachada.
11224
+ */
11225
+ function useTextareaAutosize(nodeRef, { enabled, minRows, maxRows }) {
11226
+ const heightRef = $8WNJc$react.useRef(0);
11227
+ const overflowRef = $8WNJc$react.useRef(null);
11228
+ const resize = $8WNJc$react.useCallback(() => {
11229
+ const node = nodeRef.current;
11230
+ if (!(enabled && node)) {
11231
+ return;
11232
+ }
11233
+ const result = measureTextareaHeight(node, minRows, maxRows);
11234
+ if (!result) {
11235
+ return;
11236
+ }
11237
+ if (heightRef.current !== result.height) {
11238
+ heightRef.current = result.height;
11239
+ node.style.setProperty("height", `${result.height}px`, "important");
11240
+ }
11241
+ if (overflowRef.current !== result.overflows) {
11242
+ overflowRef.current = result.overflows;
11243
+ node.style.setProperty("overflow-y", result.overflows ? "auto" : "hidden");
11244
+ }
11245
+ }, [enabled, minRows, maxRows, nodeRef]);
11246
+ // La función más reciente para los listeners, que se registran una sola vez.
11247
+ const resizeRef = $8WNJc$react.useRef(resize);
11248
+ resizeRef.current = resize;
11249
+ // Cada render, como v2: el valor controlado, el `size` o la clase pueden haber cambiado.
11250
+ useIsomorphicLayoutEffect(() => {
11251
+ resize();
11252
+ });
11253
+ // Al apagar el autosize, se retira lo que se escribió a mano para que mande el CSS.
11254
+ useIsomorphicLayoutEffect(() => {
11255
+ const node = nodeRef.current;
11256
+ if (enabled || !node) {
11257
+ return;
11258
+ }
11259
+ node.style.removeProperty("height");
11260
+ node.style.removeProperty("overflow-y");
11261
+ heightRef.current = 0;
11262
+ overflowRef.current = null;
11263
+ }, [enabled, nodeRef]);
11264
+ $8WNJc$react.useEffect(() => {
11265
+ const node = nodeRef.current;
11266
+ if (!(enabled && node)) {
11267
+ return;
11268
+ }
11269
+ const cleanups = [];
11270
+ // Ancho: re-ajuste de línea. Solo cuando cambia el ANCHO, no el alto que ponemos nosotros.
11271
+ if (typeof ResizeObserver !== "undefined") {
11272
+ let lastWidth = node.getBoundingClientRect().width;
11273
+ const observer = new ResizeObserver((entries) => {
11274
+ const width = entries[0]?.contentRect.width ?? lastWidth;
11275
+ if (width !== lastWidth) {
11276
+ lastWidth = width;
11277
+ resizeRef.current();
11278
+ }
11279
+ });
11280
+ observer.observe(node);
11281
+ cleanups.push(() => observer.disconnect());
11282
+ }
11283
+ else {
11284
+ const onWindowResize = () => resizeRef.current();
11285
+ window.addEventListener("resize", onWindowResize);
11286
+ cleanups.push(() => window.removeEventListener("resize", onWindowResize));
11287
+ }
11288
+ const fonts = document.fonts;
11289
+ if (fonts && typeof fonts.addEventListener === "function") {
11290
+ const onFonts = () => resizeRef.current();
11291
+ fonts.addEventListener("loadingdone", onFonts);
11292
+ cleanups.push(() => fonts.removeEventListener("loadingdone", onFonts));
11293
+ }
11294
+ const onReset = (event) => {
11295
+ if (node.form && node.form === event.target) {
11296
+ requestAnimationFrame(() => resizeRef.current());
11297
+ }
11298
+ };
11299
+ document.body.addEventListener("reset", onReset);
11300
+ cleanups.push(() => document.body.removeEventListener("reset", onReset));
11301
+ return () => {
11302
+ for (const cleanup of cleanups) {
11303
+ cleanup();
11304
+ }
11305
+ };
11306
+ }, [enabled, nodeRef]);
11307
+ return resize;
11308
+ }
11309
+
9461
11310
  // TECH-2459 · F3 · G1-forms — v3 divide el `Textarea` monolítico de v2 en `TextField` +
9462
11311
  // `TextArea` (el `<textarea>` real, sin `InputGroup` intermedio: a diferencia de Input, aquí el
9463
11312
  // borde/fondo/tamaño de texto viven todos en el MISMO elemento — ver `textarea.css`). Por eso no
@@ -9496,6 +11345,8 @@ const FIELD_BORDER_Y = 4;
9496
11345
  // El default de v2, literal: `({ minRows = 3, maxRows = 8, … })` en `@heroui/input@2.4.22`,
9497
11346
  // `dist/chunk-RIE4DNUQ.mjs`.
9498
11347
  const DEFAULT_MIN_ROWS = 3;
11348
+ // Mismo literal de v2: `maxRows = 8`. Tope del autocrecimiento cuando el consumidor no lo pasa.
11349
+ const DEFAULT_MAX_ROWS = 8;
9499
11350
  // ── `minRows` gana al atributo nativo `rows` ─────────────────────────────────────────────────
9500
11351
  //
9501
11352
  // Las dos props dicen lo mismo —cuántas líneas de alto tiene la caja al aparecer— así que la
@@ -9533,7 +11384,7 @@ Math.max(1, Math.round(minRows ?? rows ?? DEFAULT_MIN_ROWS));
9533
11384
  // esquina. Es el único resto que quedaba entre `textarea--default` y su referencia: 12 píxeles en
9534
11385
  // x[780..786] y[407..413], gris `102,102,102` donde v2 tiene el fondo.
9535
11386
  const RESIZE_CLASS = "resize-none";
9536
- const RADIUS_CLASS$3 = {
11387
+ const RADIUS_CLASS$2 = {
9537
11388
  none: "rounded-none",
9538
11389
  sm: "rounded-[8px]",
9539
11390
  md: "rounded-[12px]",
@@ -9614,6 +11465,28 @@ const VARIANT_CLASS = {
9614
11465
  // placeholder en el gris `default-500` (`113,113,122`) pese a ser un campo inválido.
9615
11466
  const INVALID_LABEL_CLASS = "text-danger";
9616
11467
  const INVALID_TEXT_CLASS = "text-danger";
11468
+ // ── `isClearable` / `onClear` / `classNames.clearButton` (API v2, repuesta) ──────────────────
11469
+ //
11470
+ // El `Textarea` de v2 ES el `Input` con `isMultiline` (mismo `useInput`), así que heredaba su botón
11471
+ // de limpiar: `isClearable = !!onClear || originalProps.isClearable` y `handleClear` pone el valor
11472
+ // a "" (lo que dispara `onValueChange("")`), llama a `onClear` y devuelve el foco
11473
+ // (`@heroui/input@2.4.33`, `dist/chunk-KQW2TNLT.mjs`); Escape con valor también limpia. Uso real:
11474
+ // `campaigns/**/AudienceReachStep.tsx` en beweossmbs (`isClearable` + `onClear` +
11475
+ // `classNames.clearButton`), el único de los 33 `Textarea` de la app que lo pide.
11476
+ //
11477
+ // Posición, literal del `tv` de v2 (`@heroui/theme@2.4.15`, `dist/components/input.js`):
11478
+ // · `isMultiline.clearButton` → "absolute top-2 right-2 rtl:right-auto rtl:left-2 z-10": la X va
11479
+ // ARRIBA a la derecha, no centrada como en el campo de una línea (aquí `end-2` = right/left
11480
+ // según la dirección, lo mismo que el par `right-2 rtl:left-2`);
11481
+ // · `isClearable.input` → "pe-6": el texto deja sitio a la X aunque todavía no se vea,
11482
+ // para que no salte al aparecer.
11483
+ // Lo relativo en v2 era el `inputWrapper`, que aquí no existe (el `<textarea>` es la caja), así que
11484
+ // se añade un envoltorio `relative` SOLO cuando el campo es limpiable: sin la prop el DOM no cambia.
11485
+ // El glifo es el `CloseButton` de v3, el mismo que ya usa la fachada `Input` (§9.1: verse como v3),
11486
+ // y `classNames.clearButton` va el último para que las clases del consumidor ganen.
11487
+ const CLEAR_WRAPPER_CLASS = "relative w-full";
11488
+ const CLEAR_BUTTON_CLASS = "absolute top-2 end-2 z-10 h-4 w-4 min-h-0 p-0.5";
11489
+ const CLEARABLE_FIELD_CLASS = "pe-6";
9617
11490
  // ── El `className` del consumidor: FUSIONADO y en la RAÍZ ────────────────────────────────────
9618
11491
  //
9619
11492
  // Mismo defecto doble que en `Input.tsx` (ver ahí el razonamiento completo y las medidas), porque
@@ -9651,7 +11524,15 @@ const INVALID_TEXT_CLASS = "text-danger";
9651
11524
  const Textarea = $8WNJc$react.forwardRef(({ label, description, errorMessage, id, size = "md", variant = "bordered", radius = "md",
9652
11525
  // Sin default en el destructuring a propósito: hace falta distinguir "no me la han pasado"
9653
11526
  // de un valor, para que `rows` solo se mire cuando `minRows` no viene (ver `resolveRows`).
9654
- minRows, rows, placeholder, isInvalid = false, isRequired = false, isDisabled = false, isReadOnly = false,
11527
+ minRows, rows,
11528
+ // Tope del autocrecimiento, default de v2 (8). Se desestructura también para que no
11529
+ // llegue al DOM (antes viajaba en `...rest` y React avisaba de un atributo desconocido).
11530
+ maxRows = DEFAULT_MAX_ROWS,
11531
+ // Compat v2: `true` apaga el autocrecimiento y deja la caja fija en su alto mínimo.
11532
+ disableAutosize = false, placeholder, isClearable: isClearableProp = false, onClear,
11533
+ // Sin efecto (ver `TextareaProps.labelPlacement`): fuera del spread para que no llegue
11534
+ // al `<textarea>` como atributo desconocido.
11535
+ labelPlacement: _labelPlacement, isInvalid = false, isRequired = false, isDisabled = false, isReadOnly = false,
9655
11536
  // `fullWidth` por defecto a `true`: HeroUI v2 lo traía en sus `defaultVariants`
9656
11537
  // (verificado en el paquete v2 compilado, `dist/components/input.js`:
9657
11538
  // `defaultVariants: { variant:"flat", color:"default", size:"md", fullWidth:true, … }`, con
@@ -9659,10 +11540,35 @@ minRows, rows, placeholder, isInvalid = false, isRequired = false, isDisabled =
9659
11540
  // `undefined` y el campo dejaba de llenar su contenedor: en una fila flex se encogía al ancho
9660
11541
  // de su contenido (medido por S2: 208px dentro de una fila de 592, 336px de hueco). En una
9661
11542
  // columna no se notaba porque el `stretch` del flex lo tapaba — pero afecta a cualquier app.
9662
- fullWidth = true, value, defaultValue, onChange, onValueChange, className, classNames, name, style, ...rest }, ref) => {
11543
+ fullWidth = true, value, defaultValue, onChange, onValueChange, className, classNames, name, style, onKeyDown, ...rest }, ref) => {
9663
11544
  const generatedId = $8WNJc$react.useId();
9664
11545
  const textareaId = id || generatedId;
9665
- const fieldRows = resolveRows(minRows, rows);
11546
+ const requestedRows = resolveRows(minRows, rows);
11547
+ const maxFieldRows = Math.max(1, Math.round(maxRows));
11548
+ // Con autocrecimiento, `maxRows` es el techo también del mínimo: v2 acotaba
11549
+ // `min(máx, max(mín, contenido))`, así que con `maxRows < minRows` ganaba `maxRows`. Sin
11550
+ // autocrecimiento `maxRows` no pinta nada (v2 ni se lo pasaba al `<textarea>` plano).
11551
+ const fieldRows = disableAutosize
11552
+ ? requestedRows
11553
+ : Math.min(requestedRows, maxFieldRows);
11554
+ const innerRef = $8WNJc$react.useRef(null);
11555
+ const setRefs = $8WNJc$react.useCallback((node) => {
11556
+ innerRef.current = node;
11557
+ if (typeof ref === "function") {
11558
+ ref(node);
11559
+ }
11560
+ else if (ref) {
11561
+ ref.current = node;
11562
+ }
11563
+ }, [ref]);
11564
+ const resize = useTextareaAutosize(innerRef, {
11565
+ enabled: !disableAutosize,
11566
+ minRows: fieldRows,
11567
+ maxRows: maxFieldRows,
11568
+ });
11569
+ // El `min-height` se emite aunque haya autocrecimiento: es el alto correcto ANTES de la
11570
+ // primera medida (SSR, o sin hoja de estilos) y el suelo si la medida sale menor (p. ej.
11571
+ // `flat`, sin los 4px de borde). El `height` lo escribe después `useTextareaAutosize`.
9666
11572
  // Inline y no una clase: el valor es un número arbitrario y Tailwind 4 solo emite las clases
9667
11573
  // que encuentra escritas en el fuente, así que `min-h-[${n}px]` no existiría en el CSS. El
9668
11574
  // `style` del consumidor va DESPUÉS para que pueda pisarlo (y una clase con `!` también gana,
@@ -9671,17 +11577,78 @@ fullWidth = true, value, defaultValue, onChange, onValueChange, className, class
9671
11577
  minHeight: `${fieldRows * SIZE_LINE_HEIGHT[size] + FIELD_PADDING_Y + FIELD_BORDER_Y}px`,
9672
11578
  ...style,
9673
11579
  };
11580
+ // v2: `isClearable = !!onClear || originalProps.isClearable`.
11581
+ const isClearable = isClearableProp || Boolean(onClear);
11582
+ // Limpiar en modo NO controlado tiene que poder vaciar el valor, y el estado interno del
11583
+ // `TextField` no se puede escribir desde fuera: en ese caso (y solo en ese) la fachada lleva
11584
+ // el valor en su propio estado, igual que `Input.tsx`. Sin `isClearable` nada cambia: el
11585
+ // valor no controlado sigue siendo del `TextField` (y su `reset`, de `useFormReset`).
11586
+ const isControlled = value !== undefined;
11587
+ const ownsValue = isClearable && !isControlled;
11588
+ const [ownValue, setOwnValue] = $8WNJc$react.useState(defaultValue ?? "");
11589
+ const currentValue = isControlled ? value : ownsValue ? ownValue : "";
11590
+ const showClear = isClearable && !isDisabled && !isReadOnly && Boolean(currentValue);
11591
+ // Orden de v2 (`handleClear`): el valor a "" —que en v2 disparaba `onValueChange`—, luego
11592
+ // `onClear`. El `onChange` recibe un evento de valor vacío, como en `Input.tsx`, para que un
11593
+ // consumidor que solo escucha `onChange` (p. ej. `field.onChange` de react-hook-form) se entere.
11594
+ const clearValue = () => {
11595
+ if (ownsValue) {
11596
+ setOwnValue("");
11597
+ }
11598
+ onValueChange?.("");
11599
+ if (onChange) {
11600
+ const emptyEvent = {
11601
+ target: { value: "" },
11602
+ currentTarget: { value: "" },
11603
+ };
11604
+ onChange(emptyEvent);
11605
+ }
11606
+ onClear?.();
11607
+ };
11608
+ const handleClear = () => {
11609
+ clearValue();
11610
+ innerRef.current?.focus();
11611
+ };
11612
+ // Escape limpia, igual que v2 (con valor, limpiable y no de solo lectura). El `onKeyDown` del
11613
+ // consumidor se llama primero.
11614
+ const handleKeyDown = (event) => {
11615
+ onKeyDown?.(event);
11616
+ if (event.key === "Escape" && showClear) {
11617
+ clearValue();
11618
+ }
11619
+ };
11620
+ // El estado del valor lo actualiza el `TextField` con su propio `onChange` (llega al
11621
+ // `<textarea>` por contexto y `mergeProps` encadena los dos). Aquí: medir —el nodo ya trae
11622
+ // el valor nuevo, y en modo no controlado la fachada no se re-renderiza— y avisar.
9674
11623
  const handleChange = (event) => {
11624
+ resize();
9675
11625
  onChange?.(event);
9676
11626
  onValueChange?.(event.target.value);
9677
11627
  };
9678
- 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" : "",
11628
+ const field = (jsxRuntime.jsx(react.TextArea, { ...rest, ref: setRefs, rows: fieldRows, style: fieldStyle, placeholder: placeholder, onChange: handleChange, onKeyDown: handleKeyDown, className: react.cn(SIZE_TEXT_CLASS[size], RESIZE_CLASS, RADIUS_CLASS$2[radius], VARIANT_CLASS[variant], isInvalid ? INVALID_TEXT_CLASS : "", FIELD_FOCUS_CLASS, FIELD_SHADOW_CLASS,
11629
+ // Incondicional, como el slot `input` de v2: el campo llena siempre su raíz,
11630
+ // y es la raíz la que responde a `fullWidth` y al ancho del consumidor.
11631
+ "w-full", isClearable ? CLEARABLE_FIELD_CLASS : "", classNames?.input ?? classNames?.inputWrapper) }));
11632
+ return (
11633
+ // ── `value` / `defaultValue` van al `TextField`, NO al `<textarea>` ─────────────────────
11634
+ //
11635
+ // En react-aria el dueño del valor es el `TextField`: `useTextField` guarda el estado con
11636
+ // `useControlledState(value, defaultValue ?? "")` y se lo inyecta al `<textarea>` como
11637
+ // `value` por contexto (`react-aria-components/dist/private/TextField.mjs`). Antes la
11638
+ // fachada se los pasaba al `<textarea>`, así que:
11639
+ // · en modo NO controlado el `TextField` arrancaba con "" y el `<textarea>` recibía a la
11640
+ // vez `value=""` (del contexto) y `defaultValue` → React avisaba de «both value and
11641
+ // defaultValue» y el primer re-render del padre dejaba el campo VACÍO;
11642
+ // · en modo controlado funcionaba de rebote (`mergeProps` deja ganar al `value` propio),
11643
+ // pero el estado interno del `TextField` seguía en "".
11644
+ // Ahora el `TextField` tiene el valor en los dos modos, y el `reset` del formulario también
11645
+ // es suyo (`useFormReset` devuelve el estado al `defaultValue`; el autosize lo escucha).
11646
+ jsxRuntime.jsxs(react.TextField, { id: textareaId, value: ownsValue ? ownValue : value, defaultValue: ownsValue ? undefined : defaultValue,
11647
+ // Solo cuando la fachada es dueña del valor (limpiable y no controlado).
11648
+ onChange: ownsValue ? setOwnValue : undefined, name: name, isInvalid: isInvalid, isRequired: isRequired, isDisabled: isDisabled, isReadOnly: isReadOnly, className: react.cn(FIELD_STACK_CLASS, fullWidth ? "w-full" : "",
9679
11649
  // El `className` del consumidor, en la raíz y en último lugar: el slot `base` de
9680
11650
  // v2 y la posición que le deja ganar en tailwind-merge (ver bloque de arriba).
9681
- 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,
9682
- // Incondicional, como el slot `input` de v2: el campo llena siempre su raíz,
9683
- // y es la raíz la que responde a `fullWidth` y al ancho del consumidor.
9684
- "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 }))] }));
11651
+ className), children: [label && (jsxRuntime.jsx(react.Label, { className: react.cn(LABEL_CLASS, isInvalid ? INVALID_LABEL_CLASS : "", classNames?.label), children: label })), isClearable ? (jsxRuntime.jsxs("div", { className: CLEAR_WRAPPER_CLASS, children: [field, showClear && (jsxRuntime.jsx(react.CloseButton, { "aria-label": "Limpiar", className: react.cn(CLEAR_BUTTON_CLASS, classNames?.clearButton), onPress: handleClear }))] })) : (field), description && !isInvalid && (jsxRuntime.jsx(react.Description, { className: react.cn(HELPER_CLASS, "text-left", classNames?.description), children: description })), isInvalid && errorMessage && (jsxRuntime.jsx(react.FieldError, { className: react.cn(HELPER_CLASS, "text-left", classNames?.errorMessage), children: errorMessage }))] }));
9685
11652
  });
9686
11653
  Textarea.displayName = "Textarea";
9687
11654
 
@@ -11434,8 +13401,8 @@ const DrawerFiltersFooter = ({ onClearFilters, onCancel, onApplyFilters, transla
11434
13401
  /**
11435
13402
  * Header of the DrawerFilters with title and description.
11436
13403
  */
11437
- const DrawerFiltersHeader = ({ title, description, }) => {
11438
- return (jsxRuntime.jsxs("div", { className: "px-6 pt-10", children: [jsxRuntime.jsx(H2, { className: "text-xl font-semibold text-foreground mb-2", children: title }), jsxRuntime.jsx(P, { className: "text-default-500", children: description })] }));
13404
+ const DrawerFiltersHeader = ({ titleId, title, description, }) => {
13405
+ return (jsxRuntime.jsxs("div", { className: "px-6 pt-10", children: [jsxRuntime.jsx(H2, { id: titleId, className: "text-xl font-semibold text-foreground mb-2", children: title }), jsxRuntime.jsx(P, { className: "text-default-500", children: description })] }));
11439
13406
  };
11440
13407
 
11441
13408
  const NO_YEAR_CLASS = "[&_[data-type='year']]:hidden [&_[data-type='literal']:has(+[data-type='year'])]:hidden";
@@ -12114,6 +14081,11 @@ const NumberFilter = ({ filter, value, onChange }) => {
12114
14081
  */
12115
14082
  const DrawerFilters = ({ isOpen, onOpenChange, config, onApplyFilters, onClearFilters, onCancel, className, translations, initialValues, }) => {
12116
14083
  const [filterValues, setFilterValues] = $8WNJc$react.useState(initialValues ?? {});
14084
+ // Nombre accesible del panel: su título. Con `Drawer.Root`, el `DialogTrigger` nombraba el
14085
+ // diálogo con el id de su disparador (que aquí no existe); sin root nadie lo nombra y
14086
+ // react-aria avisa en cada apertura («A dialog must have a title for accessibility», medido en
14087
+ // jsdom). Mismo arreglo que `headerId` en la fachada `Drawer`.
14088
+ const titleId = $8WNJc$react.useId();
12117
14089
  // Handle changes in individual filters
12118
14090
  const handleFilterChange = $8WNJc$react.useCallback((filterKey, value) => {
12119
14091
  setFilterValues((prev) => ({
@@ -12226,9 +14198,16 @@ const DrawerFilters = ({ isOpen, onOpenChange, config, onApplyFilters, onClearFi
12226
14198
  onApplyFilters(processedFilters);
12227
14199
  onOpenChange(false);
12228
14200
  }, [filterValues, config.data, onApplyFilters, onOpenChange]);
12229
- return (jsxRuntime.jsx(react.Drawer, { children: jsxRuntime.jsx(react.Drawer.Backdrop, { isOpen: isOpen, onOpenChange: handleOpenChange, isDismissable: false, children: jsxRuntime.jsx(react.Drawer.Content, { placement: "right", className: react.cn("drawer-filters", className), children: jsxRuntime.jsxs(react.Drawer.Dialog, { className: "w-full p-0 sm:max-w-md", children: [jsxRuntime.jsx(react.Drawer.CloseTrigger, {}), jsxRuntime.jsx(DrawerFiltersHeader, { title: config.title, description: config.description }), jsxRuntime.jsx(react.Drawer.Body, { className: "px-6 py-10", children: jsxRuntime.jsx("div", { className: "space-y-3", children: config.data.map((filter) => {
12230
- return (jsxRuntime.jsx(FilterSection, { filter: filter, translations: translations, value: filterValues[filter.key], onChange: (value) => handleFilterChange(filter.key, value) }, filter.key));
12231
- }) }) }), jsxRuntime.jsx(DrawerFiltersFooter, { onClearFilters: handleClearFilters, onCancel: handleCancel, onApplyFilters: handleApplyFilters, translations: translations })] }) }) }) }));
14201
+ // Sin `Drawer.Root` (el `DialogTrigger` de react-aria) a propósito, igual que la fachada
14202
+ // `Drawer` (`src/components/drawer/Drawer.tsx`): aquí no hay disparador —lo abre el `isOpen`
14203
+ // de la app— y un `DialogTrigger` sin hijo pulsable avisa en consola en CADA montaje, abierto o
14204
+ // cerrado («A PressResponder was rendered without a pressable child», medido en jsdom y por
14205
+ // beweossmbs con la .140). `Drawer.Backdrop` (`ModalOverlay`) ya crea su propio estado de
14206
+ // overlay con `isOpen`/`onOpenChange` y calcula todos los slots de `drawerVariants()`, así que
14207
+ // el root no aportaba nada.
14208
+ return (jsxRuntime.jsx(react.Drawer.Backdrop, { isOpen: isOpen, onOpenChange: handleOpenChange, isDismissable: false, children: jsxRuntime.jsx(react.Drawer.Content, { placement: "right", className: react.cn("drawer-filters", className), children: jsxRuntime.jsxs(react.Drawer.Dialog, { "aria-labelledby": titleId, className: "w-full p-0 sm:max-w-md", children: [jsxRuntime.jsx(react.Drawer.CloseTrigger, {}), jsxRuntime.jsx(DrawerFiltersHeader, { titleId: titleId, title: config.title, description: config.description }), jsxRuntime.jsx(react.Drawer.Body, { className: "px-6 py-10", children: jsxRuntime.jsx("div", { className: "space-y-3", children: config.data.map((filter) => {
14209
+ return (jsxRuntime.jsx(FilterSection, { filter: filter, translations: translations, value: filterValues[filter.key], onChange: (value) => handleFilterChange(filter.key, value) }, filter.key));
14210
+ }) }) }), jsxRuntime.jsx(DrawerFiltersFooter, { onClearFilters: handleClearFilters, onCancel: handleCancel, onApplyFilters: handleApplyFilters, translations: translations })] }) }) }));
12232
14211
  };
12233
14212
  DrawerFilters.displayName = "DrawerFilters";
12234
14213
 
@@ -13940,10 +15919,12 @@ const createCurrencyFormatter = (locale, currency, fractionDigits = 0) => {
13940
15919
  return (val) => formatter.format(val);
13941
15920
  };
13942
15921
 
15922
+ const ModalFacadeContext = $8WNJc$react.createContext({});
15923
+
13943
15924
  // v2 no tiene prop `radius` en el diálogo del modal: v3 tampoco (eliminada, "usar Tailwind"), así
13944
15925
  // que se reimplementa vía className arbitrario — mismo criterio y mismos valores exactos que
13945
15926
  // Button/Card de F3.0 (RADIUS_CLASS).
13946
- const RADIUS_CLASS$2 = {
15927
+ const RADIUS_CLASS$1 = {
13947
15928
  none: "rounded-none",
13948
15929
  sm: "rounded-[8px]",
13949
15930
  md: "rounded-[12px]",
@@ -14001,6 +15982,49 @@ const MAX_WIDTH_CLASS = {
14001
15982
  * `ModalContent` tenga que ser hijo directo de `Modal.Dialog`.
14002
15983
  */
14003
15984
  const ModalCloseContext = $8WNJc$react.createContext(() => { });
15985
+ /**
15986
+ * Atributos que el `Dialog` de react-aria SÍ deja pasar a su `<section>`: `id`, `role` (vía
15987
+ * `useDialog`), `data-*` y los cuatro "labelable" (`filterDOMProps(props, { labelable: true })`
15988
+ * en `react-aria/dist/private/utils/filterDOMProps.mjs`). Estos van como props para que
15989
+ * react-aria los vea — sobre todo `aria-label`/`aria-labelledby`, que deciden si avisa de un
15990
+ * diálogo sin nombre.
15991
+ */
15992
+ const LABELABLE_PROPS = new Set([
15993
+ "aria-label",
15994
+ "aria-labelledby",
15995
+ "aria-describedby",
15996
+ "aria-details",
15997
+ ]);
15998
+ /**
15999
+ * Reparte el resto de props de `Modal` entre las dos vías de llegada al diálogo. En v2 TODAS
16000
+ * llegaban (`mergeProps(modalProps, otherProps, …)` sobre el `<section>`, sin filtrar); en v3 el
16001
+ * `Dialog` filtra, así que un `aria-busy` se perdería en silencio si solo fuera como prop. Lo que
16002
+ * no es `aria-*`/`data-*`/`id`/`role` se descarta: `ModalProps` no lo declara y v2 lo habría
16003
+ * volcado al DOM con el aviso de React de atributo desconocido.
16004
+ */
16005
+ const splitDialogDOMProps = (rest) => {
16006
+ const dialogProps = {};
16007
+ const extraAria = {};
16008
+ for (const [key, value] of Object.entries(rest)) {
16009
+ // Un `aria-labelledby={undefined}` explícito no debe borrar el nombre por defecto.
16010
+ if (value === undefined) {
16011
+ continue;
16012
+ }
16013
+ if (key === "id" ||
16014
+ key === "role" ||
16015
+ key.startsWith("data-") ||
16016
+ LABELABLE_PROPS.has(key)) {
16017
+ dialogProps[key] = value;
16018
+ }
16019
+ else if (key.startsWith("aria-")) {
16020
+ extraAria[key] = value;
16021
+ }
16022
+ }
16023
+ return {
16024
+ dialogProps: dialogProps,
16025
+ extraAria: extraAria,
16026
+ };
16027
+ };
14004
16028
  /**
14005
16029
  * Modal - fachada BeweOS sobre el `Modal` compound de HeroUI v3.
14006
16030
  *
@@ -14008,15 +16032,16 @@ const ModalCloseContext = $8WNJc$react.createContext(() => { });
14008
16032
  * Dialog/Header/Body/Footer/CloseTrigger`, ver `upload-file/UploadFile.tsx` D-G1-7 y la guía
14009
16033
  * oficial `modal`). Esta fachada arma esa anatomía por dentro pero conserva la API PLANA de v2
14010
16034
  * (`isOpen`/`onOpenChange`/`onClose`/`size`/`radius`/`backdrop`/`scrollBehavior`/`placement`/
14011
- * `isDismissable`/`hideCloseButton` + `<ModalContent>{(onClose) => ...}</ModalContent>`), para que
14012
- * el código ya escrito en las 4 apps consumidoras siga funcionando sin cambios.
16035
+ * `isDismissable`/`hideCloseButton` + `<ModalContent>{(onClose) => ...}</ModalContent>`), más
16036
+ * `classNames` por slot y `aria-*`/`data-*`/`id`/`role` al diálogo, para que el código ya escrito
16037
+ * en las 4 apps consumidoras siga funcionando sin cambios.
14013
16038
  *
14014
16039
  * Accesibilidad (criterio de aceptación de la issue, verificado con Playwright): el foco entra al
14015
16040
  * abrir, queda atrapado dentro del diálogo, Escape cierra (salvo `isKeyboardDismissDisabled`), el
14016
16041
  * foco vuelve al disparador al cerrar y la página no hace scroll por detrás — los cuatro los
14017
16042
  * gestiona react-aria-components internamente (`ModalOverlay`/`Dialog`), sin código propio.
14018
16043
  */
14019
- const Modal = ({ isOpen, defaultOpen, onOpenChange, onClose, size = "md", radius = "lg", backdrop = "opaque", scrollBehavior = "normal", placement, isDismissable = true, isKeyboardDismissDisabled = false, hideCloseButton = false, className, children, }) => {
16044
+ 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 }) => {
14020
16045
  // v2 aceptaba `onClose` además de `onOpenChange` y lo llamaba en cada cierre; v3 solo conoce
14021
16046
  // `onOpenChange`. Sin este puente, un modal controlado con `isOpen` + `onClose` (el patrón
14022
16047
  // v2 dominante en las apps: ~118 usos en beweossmbs frente a 8 de `onOpenChange`) no se
@@ -14034,23 +16059,93 @@ const Modal = ({ isOpen, defaultOpen, onOpenChange, onClose, size = "md", radius
14034
16059
  onClose?.();
14035
16060
  }
14036
16061
  };
14037
- return (jsxRuntime.jsx(react.Modal, { children: jsxRuntime.jsx(react.Modal.Backdrop, { isOpen: isOpen, defaultOpen: defaultOpen, onOpenChange: handleOpenChange, 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] })) }) }) }) }));
16062
+ // v2 nombraba el diálogo con su cabecera (`aria-labelledby` = id del `ModalHeader`,
16063
+ // `getDialogProps` de `@heroui/modal@2.2.19`); en v3 solo lo hace `Modal.Heading`, que las apps
16064
+ // no usan. Mismo arreglo que `Drawer`: `ModalHeader` pone este id en su nodo.
16065
+ //
16066
+ // Si el consumidor trae su propio `aria-label`/`aria-labelledby` (3 `<Modal>` de beweossmbs,
16067
+ // p. ej. `create-note-modal`), manda el suyo. Aquí v3 mejora a v2: v2 escribía
16068
+ // `aria-labelledby` DESPUÉS del spread, así que pisaba siempre el del consumidor.
16069
+ const headerId = $8WNJc$react.useId();
16070
+ // Props de `ModalContent` (registradas desde dentro del diálogo, ver `ModalContent.tsx`). En
16071
+ // v2 `ModalContent` era el diálogo: `getDialogProps(mergeProps(dialogProps, otherProps))`
16072
+ // (`@heroui/modal@2.2.19`, `chunk-UKDXCIYN.mjs`), y dentro de `getDialogProps`
16073
+ // (`chunk-Z2LRST4N.mjs`):
16074
+ // - atributos: `mergeProps(modalProps, <resto de Modal>, <props de ModalContent>)` →
16075
+ // ModalContent gana a Modal;
16076
+ // - clase: `slots.base({ class: clsx(clsx(classNames.base, className), content.className) })`
16077
+ // → fachada (tv) < `classNames.base` < `className` de Modal < `className` de ModalContent,
16078
+ // con `twMerge`. Es el orden del `cn` de abajo.
16079
+ const [content, setContent] = $8WNJc$react.useState({});
16080
+ const registerContentProps = $8WNJc$react.useCallback((props) => setContent(props ?? {}), []);
16081
+ const { className: contentClassName, ...contentRest } = content;
16082
+ const { dialogProps, extraAria } = splitDialogDOMProps({
16083
+ ...rest,
16084
+ ...contentRest,
16085
+ });
16086
+ const hasOwnName = dialogProps["aria-label"] !== undefined ||
16087
+ dialogProps["aria-labelledby"] !== undefined;
16088
+ const hasExtraAria = Object.keys(extraAria).length > 0;
16089
+ // Sin `Modal.Root` (el `DialogTrigger` de react-aria) a propósito, igual que `Drawer`: la
16090
+ // fachada no tiene disparador propio —la abre el `isOpen` de la app— y un `DialogTrigger`
16091
+ // sin hijo pulsable avisaba en consola en CADA montaje («A PressResponder was rendered
16092
+ // without a pressable child», medido en jsdom: 9 avisos en `Modal.onClose.test.tsx`).
16093
+ // `Modal.Backdrop` (`ModalOverlay`) ya crea y publica su propio estado de overlay con
16094
+ // `isOpen`/`onOpenChange` —el que usan la X y el `close` del render-prop— y calcula todos los
16095
+ // slots de `modalVariants()` aunque no haya root encima; el root solo aportaba ese aviso y un
16096
+ // `aria-labelledby` de reserva que apuntaba al id de un disparador que no existe.
16097
+ return (jsxRuntime.jsx(react.Modal.Backdrop, { isOpen: isOpen, defaultOpen: defaultOpen, onOpenChange: handleOpenChange, variant: backdrop, isDismissable: isDismissable, isKeyboardDismissDisabled: isKeyboardDismissDisabled,
16098
+ // v2 `classNames.backdrop` → la capa que oscurece la página. Utilidad en
16099
+ // `@layer utilities`, gana a `.modal__backdrop--opaque` (`@layer components`).
16100
+ className: classNames?.backdrop, children: jsxRuntime.jsx(react.Modal.Container, { size: SIZE_MAP[size], placement: placement, scroll: scrollBehavior === "inside" ? "inside" : undefined,
16101
+ // v2 `classNames.wrapper` (el `div` que posiciona el diálogo, `data-slot="wrapper"`)
16102
+ // es el `Modal.Container` de v3: su `p-4 sm:p-10` es lo que pisa un `wrapper="p-0"`.
16103
+ className: classNames?.wrapper, children: jsxRuntime.jsx(react.Modal.Dialog, { ...(hasOwnName ? {} : { "aria-labelledby": headerId }), ...dialogProps,
16104
+ // Los `aria-*` que react-aria filtra (`aria-busy`…) entran por el `render` del
16105
+ // `<section>`: van ANTES de sus props, así que `role`/`aria-modal`/`tabIndex`
16106
+ // de react-aria no se pueden pisar por esta vía.
16107
+ render: hasExtraAria
16108
+ ? (sectionProps) => jsxRuntime.jsx("section", { ...extraAria, ...sectionProps })
16109
+ : 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 })) }) }) }));
16110
+ };
16111
+ /** Aísla el `useMemo` del valor de contexto (el render-prop de `Dialog` no puede usar hooks). */
16112
+ const ModalDialogBody = ({ close, headerId, classNames, registerContentProps, hideCloseButton, children, }) => {
16113
+ const value = $8WNJc$react.useMemo(() => ({ headerId, classNames, registerContentProps }), [headerId, classNames, registerContentProps]);
16114
+ 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] }) }));
14038
16115
  };
14039
16116
 
16117
+ /**
16118
+ * Clave estable de las props registradas: solo primitivos (`className`, `aria-*`, `data-*`…), así
16119
+ * que basta con serializarlas. Evita re-registrar —y re-renderizar el `Modal`— en cada render.
16120
+ */
16121
+ const keyOf = (props) => JSON.stringify(Object.entries(props)
16122
+ .filter(([, value]) => value !== undefined)
16123
+ .sort(([a], [b]) => (a < b ? -1 : 1)));
14040
16124
  /**
14041
16125
  * `ModalContent` de v2 no sobrevive en v3 (compound: `Modal.Container`/`Modal.Dialog`, ver D-G1-7
14042
- * / D-F2-3). Este componente NO es un wrapper de ningún compound v3 — es puro azúcar de
14043
- * compatibilidad: envuelve sus `children` (que ya se renderizan dentro de `Modal.Dialog`, ver
14044
- * `Modal.tsx`) y, si `children` es una función, la invoca con el `close` real del diálogo (vía
14045
- * contexto) para que el patrón v2 `{(onClose) => <>...</>}` siga funcionando tal cual.
16126
+ * / D-F2-3). Este componente NO es un wrapper de ningún compound v3 — es azúcar de
16127
+ * compatibilidad: sus `children` ya se renderizan dentro de `Modal.Dialog` (ver `Modal.tsx`) y, si
16128
+ * `children` es una función, la invoca con el `close` real del diálogo (vía contexto) para que el
16129
+ * patrón v2 `{(onClose) => <>...</>}` siga funcionando tal cual.
16130
+ *
16131
+ * Sus demás props (`className`, `aria-busy`, `data-*`…) NO se pintan aquí: en v2 `ModalContent`
16132
+ * era el propio diálogo, así que se registran en el `Modal` y este las aplica al `role="dialog"`
16133
+ * (antes caían en un `<div>` intermedio y, p. ej., el `h-[95vh] p-0 rounded-2xl` de
16134
+ * `chatbot-config` no llegaba al diálogo). Sin props no se registra nada: el render es el mismo.
14046
16135
  */
14047
- function ModalContent({ children, className }) {
16136
+ function ModalContent({ children, ...dialogProps }) {
14048
16137
  const close = $8WNJc$react.useContext(ModalCloseContext);
14049
- const content = typeof children === "function" ? children(close) : children;
14050
- if (!className) {
14051
- return jsxRuntime.jsx(jsxRuntime.Fragment, { children: content });
14052
- }
14053
- return jsxRuntime.jsx("div", { className: className, children: content });
16138
+ const { registerContentProps } = $8WNJc$react.useContext(ModalFacadeContext);
16139
+ const key = keyOf(dialogProps);
16140
+ // biome-ignore lint/correctness/useExhaustiveDependencies: `key` resume `dialogProps` (primitivos); depender del objeto re-registraría en cada render.
16141
+ $8WNJc$react.useLayoutEffect(() => {
16142
+ if (!registerContentProps || key === "[]") {
16143
+ return;
16144
+ }
16145
+ registerContentProps(dialogProps);
16146
+ return () => registerContentProps(undefined);
16147
+ }, [registerContentProps, key]);
16148
+ return jsxRuntime.jsx(jsxRuntime.Fragment, { children: typeof children === "function" ? children(close) : children });
14054
16149
  }
14055
16150
 
14056
16151
  /**
@@ -14062,9 +16157,23 @@ function ModalContent({ children, className }) {
14062
16157
  * era una fila sin gap.
14063
16158
  *
14064
16159
  * El `className` del consumidor se mezcla con `cn` (tailwind-merge), así que sigue
14065
- * ganando: `<ModalHeader className="flex flex-col gap-1">` mantiene su columna.
16160
+ * ganando: `<ModalHeader className="flex flex-col gap-1">` mantiene su columna. El slot
16161
+ * `classNames.header` del `<Modal>` se aplica entre medias (gana a la fachada, pierde con el
16162
+ * `className`), y el nodo lleva el `id` que nombra el diálogo, como en v2.
14066
16163
  */
14067
- 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) }));
16164
+ const ModalHeader = ({ className, ...props }) => {
16165
+ const { headerId, classNames } = $8WNJc$react.useContext(ModalFacadeContext);
16166
+ return (jsxRuntime.jsx(react.ModalHeader
16167
+ // Nombra el diálogo (`aria-labelledby`, ver `Modal.tsx`). Va antes del spread: un `id`
16168
+ // propio del consumidor gana, igual que en v2 (`id: headerId, ...otherProps`).
16169
+ , {
16170
+ // Nombra el diálogo (`aria-labelledby`, ver `Modal.tsx`). Va antes del spread: un `id`
16171
+ // propio del consumidor gana, igual que en v2 (`id: headerId, ...otherProps`).
16172
+ id: headerId, ...props, className: react.cn("flex-row gap-0 px-6 py-4 text-lg font-semibold",
16173
+ // v2 `classNames.header` del `<Modal>`; el `className` propio gana
16174
+ // (`clsx(classNames.header, className)` en `@heroui/modal@2.2.19`).
16175
+ classNames?.header, className) }));
16176
+ };
14068
16177
  ModalHeader.displayName = "ModalHeader";
14069
16178
 
14070
16179
  /**
@@ -14082,6 +16191,7 @@ ModalHeader.displayName = "ModalHeader";
14082
16191
  */
14083
16192
  const BODY_V2_GEOMETRY$1 = "mx-0 mt-0 flex flex-1 flex-col gap-3 px-6 py-2";
14084
16193
  const ModalBody = $8WNJc$react.forwardRef(({ className, ...props }, forwardedRef) => {
16194
+ const { classNames } = $8WNJc$react.useContext(ModalFacadeContext);
14085
16195
  const nodeRef = $8WNJc$react.useRef(null);
14086
16196
  const callbackRef = $8WNJc$react.useCallback((node) => {
14087
16197
  nodeRef.current = node;
@@ -14227,7 +16337,10 @@ const ModalBody = $8WNJc$react.forwardRef(({ className, ...props }, forwardedRef
14227
16337
  track.remove();
14228
16338
  };
14229
16339
  }, []);
14230
- return (jsxRuntime.jsx(react.ModalBody, { ref: callbackRef, ...props, className: react.cn(BODY_V2_GEOMETRY$1, className) }));
16340
+ return (jsxRuntime.jsx(react.ModalBody, { ref: callbackRef, ...props,
16341
+ // v2 `classNames.body` del `<Modal>` entre la geometría y el `className` propio,
16342
+ // que gana (`clsx(classNames.body, className)` en `@heroui/modal@2.2.19`).
16343
+ className: react.cn(BODY_V2_GEOMETRY$1, classNames?.body, className) }));
14231
16344
  });
14232
16345
  ModalBody.displayName = "ModalBody";
14233
16346
 
@@ -14238,7 +16351,13 @@ ModalBody.displayName = "ModalBody";
14238
16351
  * `.modal__body + .modal__footer { mt-5 }` que v2 no tenía (v2 separaba los slots solo
14239
16352
  * con su propio padding) — de ahí el `mt-0`.
14240
16353
  */
14241
- const ModalFooter = ({ className, ...props }) => (jsxRuntime.jsx(react.ModalFooter, { ...props, className: react.cn("mt-0 px-6 py-4", className) }));
16354
+ const ModalFooter = ({ className, ...props }) => {
16355
+ const { classNames } = $8WNJc$react.useContext(ModalFacadeContext);
16356
+ return (jsxRuntime.jsx(react.ModalFooter, { ...props,
16357
+ // v2 `classNames.footer` del `<Modal>` entre la fachada y el `className` propio, que
16358
+ // gana (`clsx(classNames.footer, className)` en `@heroui/modal@2.2.19`).
16359
+ className: react.cn("mt-0 px-6 py-4", classNames?.footer, className) }));
16360
+ };
14242
16361
  ModalFooter.displayName = "ModalFooter";
14243
16362
 
14244
16363
  const DrawerFacadeContext = $8WNJc$react.createContext({
@@ -14472,7 +16591,7 @@ const COLOR_BY_STATUS = {
14472
16591
  };
14473
16592
  // v3 usa `min(32px, var(--radius-3xl))`; v2 `radius="md"` = `rounded-medium` = 12px. Mismos
14474
16593
  // valores EXACTOS que Button/Card/Chip (los `rounded-md`/`lg` de Tailwind son 6/8px, no 12/14).
14475
- const RADIUS_CLASS$1 = {
16594
+ const RADIUS_CLASS = {
14476
16595
  none: "rounded-none",
14477
16596
  sm: "rounded-[8px]",
14478
16597
  md: "rounded-[12px]",
@@ -14627,7 +16746,7 @@ function slotClasses(variant, color) {
14627
16746
  }
14628
16747
  // Los subcomponentes compound de v3, adjuntos como estáticos (`Alert.Indicator`…) para que el JSX
14629
16748
  // v3 (loginos ya migró el suyo) siga compilando y renderizando IGUAL que contra el `Alert` crudo.
14630
- const COMPOUND_PARTS$1 = new Set([
16749
+ const COMPOUND_PARTS = new Set([
14631
16750
  react.Alert.Indicator,
14632
16751
  react.Alert.Content,
14633
16752
  react.Alert.Title,
@@ -14639,8 +16758,8 @@ const COMPOUND_PARTS$1 = new Set([
14639
16758
  * hiciera, saldrían dos indicadores). `<Alert>Texto</Alert>` (sin partes compound) sigue siendo
14640
16759
  * API v2: children bajo la descripción, con icono y círculo, como en v2.
14641
16760
  */
14642
- function hasCompoundChildren$1(children) {
14643
- return $8WNJc$react.Children.toArray(children).some((child) => $8WNJc$react.isValidElement(child) && COMPOUND_PARTS$1.has(child.type));
16761
+ function hasCompoundChildren(children) {
16762
+ return $8WNJc$react.Children.toArray(children).some((child) => $8WNJc$react.isValidElement(child) && COMPOUND_PARTS.has(child.type));
14644
16763
  }
14645
16764
  /** `isEmpty` de v2 (`@heroui/shared-utils`): null/undefined/"" no cuentan como contenido. */
14646
16765
  function hasValue$1(node) {
@@ -14670,7 +16789,7 @@ const AlertFacade = ({ color, status, variant = "flat", radius = "md", title, de
14670
16789
  if (!isVisible) {
14671
16790
  return null;
14672
16791
  }
14673
- if (hasCompoundChildren$1(children)) {
16792
+ if (hasCompoundChildren(children)) {
14674
16793
  return (jsxRuntime.jsx(react.Alert, { ...rest, status: effectiveStatus, className: react.cn(classNames?.base, className), children: children }));
14675
16794
  }
14676
16795
  const handleClose = () => {
@@ -14690,7 +16809,7 @@ const AlertFacade = ({ color, status, variant = "flat", radius = "md", title, de
14690
16809
  // Base v2: `flex flex-grow flex-row w-full items-start py-3 px-4 gap-x-1`. v3 ya trae
14691
16810
  // flex/w-full/items-start/px-4/py-3; se repone el `gap-x-1` (v3: `gap-4`), se quita el
14692
16811
  // `shadow-surface` (v2 no tenía sombra en el base) y se aplica variant/radius.
14693
- 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,
16812
+ 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,
14694
16813
  // v2 `hideIconWrapper`: `!bg-transparent !shadow-none !border-none` — se omiten
14695
16814
  // las clases teñidas en vez de pisarlas, porque `bg-transparent` no borraría el
14696
16815
  // `dark:bg-*` (grupo distinto para tailwind-merge).
@@ -14717,181 +16836,82 @@ const Alert = Object.assign(AlertFacade, {
14717
16836
  Description: react.Alert.Description,
14718
16837
  });
14719
16838
 
14720
- // TECH-2459 — Fachada `Checkbox` (hallazgo loginos 07-sep: `index.tsx` reexportaba el `Checkbox`
14721
- // CRUDO de v3, así que el JSX v2 de las apps — `isSelected` + `onValueChange` dentro de `Controller`
14722
- // de RHF en backoffice ×12, smbs ×23, agency ×5 — no compilaba pese a existir el símbolo).
14723
- //
14724
- // HeroUI v3 convierte el Checkbox en compound (`Checkbox.Content` > `Checkbox.Control` >
14725
- // `Checkbox.Indicator` + la etiqueta como children de `Content`), renombra `onValueChange` →
14726
- // `onChange` y ELIMINA `color`, `size`, `radius`, `lineThrough`, `icon`, `classNames` y
14727
- // `disableAnimation` (guía oficial `checkbox`). Su único eje propio es `variant`
14728
- // (`primary`/`secondary`), que NO es un color: `secondary` es un tratamiento gris de menor énfasis
14729
- // — el falso amigo de siempre (skill `auraui-design-tokens`).
14730
- //
14731
- // Esta fachada compone el compound por dentro y repone cada prop v2 con los literales del `tv`
14732
- // de v2 (`@heroui/theme@2.4.15` → `dist/components/checkbox.js`), traducidos a utilidades de
14733
- // Tailwind 4 sobre los tokens de Aurora. El relleno del estado marcado en v3 es el `::before` del
14734
- // control (`bg-accent`, = `--color-primary` vía F1), así que `color` se reimplementa pisando ese
14735
- // pseudo-elemento con `before:bg-<color>` — utilidad (capa `utilities`) que gana a la regla de
14736
- // `checkbox.css` (capa `components`), incluida la de hover (`bg-accent-hover`).
14737
- //
14738
- // ⚠️ Estados de react-aria, verificados en el navegador contra `checkbox.css`: `data-selected`/
14739
- // `data-indeterminate`/`data-invalid`/`data-disabled` viven en la RAÍZ (`.checkbox`);
14740
- // `data-hovered`/`data-pressed`/`data-focus-visible` en `[data-slot="checkbox-content"]`. Por
14741
- // eso hay dos grupos con nombre (`group/checkbox`, `group/content`) y NUNCA `data-[hover=true]`
14742
- // (trampa 3 de la skill: v2 escribía `data-hover`; react-aria escribe `data-hovered`).
14743
- /**
14744
- * Relleno del estado marcado (`::before` del control) y del indeterminado (fondo del control)
14745
- * por color. `primary` no pisa nada: el `bg-accent` de v3 YA es `--color-primary`. `default`
14746
- * usa el gris de Aurora (`--color-default`, = v2), nunca el `--default` crudo de v3 (D-F1-3).
14747
- *
14748
- * Literal v2: `after:bg-<color> after:text-<color>-foreground text-<color>-foreground`.
14749
- */
14750
- const CONTROL_COLOR = {
14751
- primary: "",
14752
- default: "before:bg-default group-data-[indeterminate=true]/checkbox:bg-default",
14753
- secondary: "before:bg-secondary group-data-[indeterminate=true]/checkbox:bg-secondary",
14754
- success: "before:bg-success group-data-[indeterminate=true]/checkbox:bg-success",
14755
- warning: "before:bg-warning group-data-[indeterminate=true]/checkbox:bg-warning",
14756
- danger: "before:bg-danger group-data-[indeterminate=true]/checkbox:bg-danger",
14757
- };
14758
- /**
14759
- * Color del glifo (check / línea). v3 lo fija en el SVG (`[data-slot="checkbox-default-indicator--
14760
- * checkmark"] { text-accent-foreground }`), así que hay que alcanzar al SVG, no al indicador.
14761
- */
14762
- const INDICATOR_COLOR = {
14763
- primary: "",
14764
- default: "[&_svg]:text-default-foreground",
14765
- secondary: "[&_svg]:text-secondary-foreground",
14766
- success: "[&_svg]:text-success-foreground",
14767
- warning: "[&_svg]:text-warning-foreground",
14768
- danger: "[&_svg]:text-danger-foreground",
14769
- };
14770
- // v2 (`checkbox.js`, variante `size`): wrapper `w-4/w-5/w-6` (16/20/24px), label
14771
- // `text-small/medium/large`, icon `w-3 h-2` / `w-4 h-3` / `w-5 h-4`. v3: control `size-4`, glifo
14772
- // `size-2.5`, texto `text-sm`, para todos.
14773
- const CONTROL_SIZE = {
14774
- sm: "size-4",
14775
- md: "size-5",
14776
- lg: "size-6",
14777
- };
14778
- const INDICATOR_SIZE = {
14779
- sm: "size-3 [&_svg]:size-2.5",
14780
- md: "size-3.5 [&_svg]:size-3",
14781
- lg: "size-4 [&_svg]:size-3.5",
14782
- };
14783
- const LABEL_SIZE = {
14784
- sm: "text-sm",
14785
- md: "text-base",
14786
- lg: "text-lg",
14787
- };
14788
- // v2: `rounded-[calc(theme(borderRadius.medium)*K)]` con `medium` = 12px → 6 / 7.2 / 8.4 px, y por
14789
- // defecto el radio lo decidía `size` (sm→0.5, md→0.6, lg→0.7). El `::before` (relleno) lleva el
14790
- // mismo radio porque aquí cubre el border-box completo (ver `CONTROL_BASE`).
14791
- const RADIUS_CLASS = {
14792
- none: "rounded-none before:rounded-none",
14793
- sm: "rounded-[6px] before:rounded-[6px]",
14794
- md: "rounded-[7.2px] before:rounded-[7.2px]",
14795
- lg: "rounded-[8.4px] before:rounded-[8.4px]",
14796
- full: "rounded-full before:rounded-full",
14797
- };
14798
- const RADIUS_BY_SIZE = {
14799
- sm: "sm",
14800
- md: "md",
14801
- lg: "lg",
14802
- };
14803
- /**
14804
- * Caja del control (slot `wrapper` de v2): borde de 2px en `--color-default` sin sombra
14805
- * (`before:border-2 before:border-default`), y el relleno cubriendo la caja ENTERA al marcarse.
14806
- *
14807
- * En v3 el borde vive en el propio control (1px, `border-field-border`, `shadow-field`) y el
14808
- * relleno es un `::before` a `inset-0`, que solo alcanza al padding-box: por eso v3 pone
14809
- * `border-transparent` al marcarse y el relleno queda 1px por dentro. Con el borde de 2px de v2
14810
- * eso dejaría un anillo de 2px: se lleva el `::before` al border-box (`-inset-0.5` = −2px) y se
14811
- * quita el `overflow-hidden` de v3 para que no lo recorte. El `::before` es un descendiente
14812
- * posicionado, así que pinta ENCIMA del borde y lo tapa, como el `after` de v2.
14813
- *
14814
- * Hover v2: `group-data-[hover=true]:before:bg-default-100` — tinte suave de la caja sin marcar
14815
- * (en v3 la caja es el fondo del control, no un pseudo). `data-hovered` lo emite `Checkbox.Content`.
14816
- *
14817
- * Indeterminado: v3 pinta el fondo del control (dentro del borde). Se enciende también el
14818
- * `::before` para que el relleno cubra la caja entera, igual que al marcar.
14819
- */
14820
- const CONTROL_BASE = [
14821
- "border-2 border-default shadow-none overflow-visible",
14822
- "before:-inset-0.5",
14823
- "group-data-[hovered=true]/content:bg-default-100",
14824
- "group-data-[indeterminate=true]/checkbox:before:scale-100",
14825
- "group-data-[indeterminate=true]/checkbox:before:opacity-100",
14826
- ].join(" ");
14827
- // v2 (`lineThrough: true`, slot `label`): la raya crece de 0 al 100% al marcarse y la etiqueta se
14828
- // atenúa al 60%. `data-selected` está en la raíz → `group-data-[…]/checkbox`.
14829
- const LINE_THROUGH = [
14830
- "inline-flex items-center justify-center",
14831
- "before:absolute before:h-0.5 before:w-0 before:bg-foreground before:content-['']",
14832
- "group-data-[selected=true]/checkbox:opacity-60",
14833
- "group-data-[selected=true]/checkbox:before:w-full",
14834
- ].join(" ");
14835
- // Los subcomponentes compound de v3, adjuntos como estáticos para que la API v3 siga funcionando
14836
- // a través de la fachada (`<Checkbox><Checkbox.Control>…`).
14837
- const COMPOUND_PARTS = new Set([
14838
- react.Checkbox.Control,
14839
- react.Checkbox.Indicator,
14840
- react.Checkbox.Content,
14841
- ]);
14842
- /** API compound de v3 detectada por hijos directos: la fachada no envuelve nada. */
14843
- function hasCompoundChildren(children) {
14844
- return $8WNJc$react.Children.toArray(children).some((child) => $8WNJc$react.isValidElement(child) && COMPOUND_PARTS.has(child.type));
14845
- }
14846
- /**
14847
- * Checkbox - Casilla basada en HeroUI v3 con la API pública de v2 intacta (TECH-2459).
14848
- *
14849
- * - **API v2**: `children` como etiqueta, `isSelected`/`defaultSelected` + `onValueChange`
14850
- * (también `onChange`), `color`, `size`, `radius`, `lineThrough`, `icon`, `classNames`. Funciona
14851
- * dentro de `Controller` de react-hook-form (`isSelected={value} onValueChange={onChange}`).
14852
- * - **API compound v3**: si los children traen `Checkbox.Control`/`Indicator`/`Content`, pasa tal
14853
- * cual a la raíz de HeroUI (con `onValueChange` seguido igualmente).
14854
- */
14855
- const CheckboxFacade = ({ children, onChange, onValueChange, color = "primary", size = "md", radius, lineThrough = false, icon, disableAnimation = false, isInvalid, classNames, className, ...rest }) => {
14856
- const handleChange = (isSelected) => {
14857
- onChange?.(isSelected);
14858
- onValueChange?.(isSelected);
16839
+ const OMIT$1 = new Set(["children", "className", "type"]);
16840
+ const CONCAT = new Set(["aria-describedby", "aria-labelledby"]);
16841
+ function mergeRefs$1(...refs) {
16842
+ return (value) => {
16843
+ for (const ref of refs) {
16844
+ if (typeof ref === "function") {
16845
+ ref(value);
16846
+ }
16847
+ else if (ref) {
16848
+ ref.current = value;
16849
+ }
16850
+ }
14859
16851
  };
14860
- if (hasCompoundChildren(children)) {
14861
- return (jsxRuntime.jsx(react.Checkbox, { ...rest, isInvalid: isInvalid, onChange: handleChange, className: react.cn(classNames?.base, className), children: children }));
16852
+ }
16853
+ function mergeTriggerProps$1(trigger, child) {
16854
+ const merged = { ...trigger, ...child };
16855
+ for (const key of Object.keys(trigger)) {
16856
+ const a = trigger[key];
16857
+ const b = child[key];
16858
+ if (/^on[A-Z]/.test(key) &&
16859
+ typeof a === "function" &&
16860
+ typeof b === "function") {
16861
+ merged[key] = (...args) => {
16862
+ a(...args);
16863
+ b(...args);
16864
+ };
16865
+ }
16866
+ else if (CONCAT.has(key) && a && b) {
16867
+ merged[key] = `${a} ${b}`;
16868
+ }
16869
+ else if (b === undefined) {
16870
+ merged[key] = a;
16871
+ }
14862
16872
  }
14863
- const indicatorContent = typeof icon === "function"
14864
- ? (state) => icon({
14865
- isSelected: state.isSelected,
14866
- isIndeterminate: state.isIndeterminate,
14867
- isDisabled: state.isDisabled,
14868
- })
14869
- : icon;
14870
- const hasLabel = children !== undefined && children !== null && children !== false;
14871
- return (jsxRuntime.jsx(react.Checkbox, { ...rest, isInvalid: isInvalid, onChange: handleChange,
14872
- // Base v2: `group relative max-w-fit inline-flex items-center justify-start cursor-pointer
14873
- // p-2 -m-2 select-none` (el `p-2 -m-2` es el área de pulsado ampliada de v2). v3
14874
- // (`.checkbox`) usa `flex flex-col items-start` para apilar `Description`/`FieldError`,
14875
- // slots que esta fachada no renderiza; en **inline** la caja participa de la línea de
14876
- // texto, donde la esperan los consumidores (mismo criterio que la fachada `Switch`).
14877
- 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],
14878
- // v2 `isInvalid`: `before:border-danger` (el borde vivía en el pseudo).
14879
- 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 && (
14880
- // Slot `label` de v2: `relative text-foreground select-none` + tamaño de texto.
14881
- // `data-slot="label"` para que las reglas de cursor/selección de `.checkbox` de v3
14882
- // le alcancen igual que a un `<Label>`.
14883
- 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 }))] }) }));
14884
- };
14885
- CheckboxFacade.displayName = "Aurora.Checkbox";
16873
+ return merged;
16874
+ }
16875
+ /** ¿Se puede clonar como trigger? Un único elemento que no sea Fragment. */
16876
+ function isClonableTriggerChild(children) {
16877
+ return $8WNJc$react.isValidElement(children) && children.type !== $8WNJc$react.Fragment;
16878
+ }
14886
16879
  /**
14887
- * `Checkbox` de Aurora: fachada v2 + estáticos compound de v3 (`Checkbox.Control`,
14888
- * `Checkbox.Indicator`, `Checkbox.Content`), los mismos objetos que exporta `@heroui/react`.
16880
+ * `render` para `Dropdown.Trigger`: clona las props resueltas del `<button>` de v3 sobre `child`.
16881
+ * `triggerClassName` es el `className` que la app puso en el propio `DropdownTrigger`.
14889
16882
  */
14890
- const Checkbox = Object.assign(CheckboxFacade, {
14891
- Control: react.Checkbox.Control,
14892
- Indicator: react.Checkbox.Indicator,
14893
- Content: react.Checkbox.Content,
14894
- });
16883
+ function renderDropdownTrigger(child, triggerClassName) {
16884
+ return (buttonProps) => {
16885
+ const { ref, ...rest } = buttonProps;
16886
+ const triggerProps = {};
16887
+ for (const [key, value] of Object.entries(rest)) {
16888
+ if (OMIT$1.has(key) || key.startsWith("data-")) {
16889
+ continue;
16890
+ }
16891
+ triggerProps[key] = value;
16892
+ }
16893
+ const hostTag = typeof child.type === "string" ? child.type : null;
16894
+ if (hostTag === "button") {
16895
+ triggerProps.type = rest.type;
16896
+ }
16897
+ else if (hostTag && hostTag !== "a") {
16898
+ triggerProps.role = "button";
16899
+ triggerProps.tabIndex = 0;
16900
+ }
16901
+ const childRef = (Number.parseInt($8WNJc$react.version, 10) < 19
16902
+ ? child.ref
16903
+ : child.props.ref);
16904
+ const childClass = child.props.className;
16905
+ const className = triggerClassName && typeof childClass !== "function"
16906
+ ? react.cn(triggerClassName, childClass)
16907
+ : childClass;
16908
+ return $8WNJc$react.cloneElement(child, {
16909
+ ...mergeTriggerProps$1(triggerProps, child.props),
16910
+ ...(className === undefined ? {} : { className }),
16911
+ ref: mergeRefs$1(childRef, ref),
16912
+ });
16913
+ };
16914
+ }
14895
16915
 
14896
16916
  // TECH-2459 — Fachada `Dropdown` / `DropdownSection`: repone la CABECERA DE SECCIÓN que la
14897
16917
  // migración a v3 dejó sin vía legal desde una app.
@@ -15033,15 +17053,153 @@ const OWN_POPOVER = new Set([
15033
17053
  function isPart$1(node, parts) {
15034
17054
  return $8WNJc$react.isValidElement(node) && parts.has(node.type);
15035
17055
  }
15036
- const DropdownFacade = ({ children, ...props }) => {
15037
- const nodes = $8WNJc$react.Children.toArray(children);
17056
+ function isFragment(node) {
17057
+ return $8WNJc$react.isValidElement(node) && node.type === $8WNJc$react.Fragment;
17058
+ }
17059
+ /**
17060
+ * `Children.toArray` aplana arrays pero NO Fragments (un `<>…</>` cuenta como UN nodo). Esto baja
17061
+ * por ellos, y prefija la `key` de cada hijo con la del Fragment para que dos Fragments hermanos
17062
+ * no produzcan claves repetidas (`toArray` numera cada nivel desde `.0`).
17063
+ */
17064
+ function flatten(children, prefix = "") {
17065
+ return $8WNJc$react.Children.toArray(children).flatMap((node) => {
17066
+ if (isFragment(node)) {
17067
+ return flatten(node.props.children, `${prefix}${String(node.key)}`);
17068
+ }
17069
+ if (prefix && $8WNJc$react.isValidElement(node)) {
17070
+ return [$8WNJc$react.cloneElement(node, { key: `${prefix}${String(node.key)}` })];
17071
+ }
17072
+ return [node];
17073
+ });
17074
+ }
17075
+ /**
17076
+ * Mapea el árbol conservando su forma: atraviesa Fragments (se reconstruyen con sus hijos
17077
+ * mapeados) y aplica `fn` al resto de nodos. Para el caso en el que el popover es del consumidor:
17078
+ * no hay que mover nada de sitio, solo completar props.
17079
+ */
17080
+ function mapTree(children, fn) {
17081
+ return $8WNJc$react.Children.toArray(children).map((node) => isFragment(node)
17082
+ ? $8WNJc$react.cloneElement(node, undefined, ...mapTree(node.props.children, fn))
17083
+ : fn(node));
17084
+ }
17085
+ /**
17086
+ * Traducción de colocación v2 → v3, copiada del oráculo, no deducida: `@heroui/aria-utils@2.2.29`
17087
+ * → `dist/chunk-WQVQ7P2I.mjs:53-69` (`toReactAriaPlacement`), que es lo que el `Popover` de v2
17088
+ * hacía con la prop antes de dársela a react-aria. Ojo a los laterales: `left-start` NO es
17089
+ * `"left start"` (no existe en react-aria) sino `"left top"`.
17090
+ */
17091
+ const V2_TO_V3_PLACEMENT = {
17092
+ top: "top",
17093
+ bottom: "bottom",
17094
+ left: "left",
17095
+ right: "right",
17096
+ "top-start": "top start",
17097
+ "top-end": "top end",
17098
+ "bottom-start": "bottom start",
17099
+ "bottom-end": "bottom end",
17100
+ "left-start": "left top",
17101
+ "left-end": "left bottom",
17102
+ "right-start": "right top",
17103
+ "right-end": "right bottom",
17104
+ };
17105
+ /** v2 → v3; lo que ya viene en sintaxis de v3 pasa tal cual. */
17106
+ function toV3Placement(placement) {
17107
+ return (V2_TO_V3_PLACEMENT[placement] ??
17108
+ placement);
17109
+ }
17110
+ /** Solo las claves que el consumidor escribió: `undefined` no pisa nada. */
17111
+ function definedOnly(values) {
17112
+ return Object.fromEntries(Object.entries(values).filter(([, value]) => value !== undefined));
17113
+ }
17114
+ /**
17115
+ * `closeOnSelect` de v2 → `shouldCloseOnSelect` del `Menu` de v3. Precedencia, la de v2
17116
+ * (`@heroui/dropdown@2.3.32` → `dist/chunk-HT5XYLWZ.mjs`, `getCloseOnSelect`): lo del menú gana a
17117
+ * lo del root. Del menú se lee `shouldCloseOnSelect` (v3) y, si no, `closeOnSelect` (v2: 6 de los
17118
+ * 20 `<DropdownMenu>` de beweossmbs lo escriben en el MENÚ, no en el root, y el `Menu` de v3 lo
17119
+ * ignora — sin esto, un multiselect se cerraba en cada clic). El `closeOnSelect` se retira del
17120
+ * elemento: v3 no lo conoce.
17121
+ */
17122
+ function withCloseOnSelect(node, rootCloseOnSelect) {
17123
+ if (!isPart$1(node, INSIDE_POPOVER)) {
17124
+ return node;
17125
+ }
17126
+ const menu = node;
17127
+ const { closeOnSelect, shouldCloseOnSelect } = menu.props;
17128
+ const resolved = shouldCloseOnSelect ?? closeOnSelect ?? rootCloseOnSelect;
17129
+ if (resolved === undefined && !("closeOnSelect" in menu.props)) {
17130
+ return node;
17131
+ }
17132
+ const { closeOnSelect: _dropped, ...rest } = menu.props;
17133
+ return $8WNJc$react.createElement(menu.type, {
17134
+ ...rest,
17135
+ key: menu.key,
17136
+ shouldCloseOnSelect: resolved,
17137
+ });
17138
+ }
17139
+ /**
17140
+ * `<DropdownTrigger><Button/></DropdownTrigger>` de v2 → UN botón, el de la app, con las props del
17141
+ * trigger de v3 clonadas encima vía su `render` (ver `_internal/renderTrigger.tsx`). Sin esto v3
17142
+ * pinta dos `<button>` anidados. Se deja el trigger de v3 tal cual si el consumidor ya pasa su
17143
+ * propio `render`, o si el hijo no es UN elemento (texto, Fragment, función de render props):
17144
+ * entonces el `<button>` de v3 es el trigger, que es lo correcto.
17145
+ *
17146
+ * Medido en beweossmbs `origin/staging` (f6d5be1c2): 20 `<DropdownTrigger>`, 19 con un `<Button>`
17147
+ * de hijo directo y 1 con `{trigger || defaultTrigger}` (también un `Button` en ejecución).
17148
+ */
17149
+ function withClonedTrigger(node) {
17150
+ if (!$8WNJc$react.isValidElement(node) || node.type !== react.Dropdown.Trigger) {
17151
+ return node;
17152
+ }
17153
+ const trigger = node;
17154
+ const { children, render, className } = trigger.props;
17155
+ if (render !== undefined || !isClonableTriggerChild(children)) {
17156
+ return node;
17157
+ }
17158
+ return $8WNJc$react.cloneElement(trigger, {
17159
+ render: renderDropdownTrigger(children, typeof className === "string" ? className : undefined),
17160
+ });
17161
+ }
17162
+ const DropdownFacade = ({ children, placement, offset, crossOffset, shouldFlip, containerPadding, closeOnSelect, shouldCloseOnScroll: _noEffectInV3, className, classNames, ...props }) => {
17163
+ const overrides = definedOnly({
17164
+ placement: placement === undefined ? undefined : toV3Placement(placement),
17165
+ offset,
17166
+ crossOffset,
17167
+ shouldFlip,
17168
+ containerPadding,
17169
+ });
17170
+ // v2: `base` (caja exterior), luego `content` + `className` (caja interior); en v3 es una sola
17171
+ // caja, así que van juntas y en ese orden. `cn` funde conflictos entre ellas; frente a la clase
17172
+ // de v3 (`.dropdown__popover`, capa `components`) cualquier utilidad ya gana por capa.
17173
+ const popoverClass = react.cn(classNames?.base, classNames?.content, className) || undefined;
17174
+ const menuWithClose = (node) => withCloseOnSelect(node, closeOnSelect);
17175
+ const nodes = flatten(children);
17176
+ // El popover es del consumidor: se respeta su árbol entero y se le COMPLETA lo que no dijo
17177
+ // (lo que sí dijo manda). Los menús dentro de él reciben el `closeOnSelect` igualmente.
17178
+ if (nodes.some((node) => isPart$1(node, OWN_POPOVER))) {
17179
+ const fillPopover = (node) => {
17180
+ if (!$8WNJc$react.isValidElement(node) || node.type !== react.Dropdown.Popover) {
17181
+ return node;
17182
+ }
17183
+ const own = node;
17184
+ const missing = Object.fromEntries(Object.entries(overrides).filter(([key]) => own.props[key] === undefined));
17185
+ const ownClass = own.props.className;
17186
+ return $8WNJc$react.cloneElement(own, {
17187
+ ...missing,
17188
+ ...(popoverClass && typeof ownClass !== "function"
17189
+ ? { className: react.cn(popoverClass, ownClass) }
17190
+ : {}),
17191
+ }, ...mapTree(own.props.children, menuWithClose));
17192
+ };
17193
+ return (jsxRuntime.jsx(react.Dropdown, { ...props, children: mapTree(children, (node) => fillPopover(withClonedTrigger(node))) }));
17194
+ }
15038
17195
  const menus = nodes.filter((node) => isPart$1(node, INSIDE_POPOVER));
15039
- // Nada que envolver (sin hijos, `children` como función, un popover ya montado a mano, o
15040
- // simplemente ningún menú suelto): el root tal cual, byte a byte como el crudo de v3.
15041
- if (menus.length === 0 || nodes.some((node) => isPart$1(node, OWN_POPOVER))) {
15042
- return jsxRuntime.jsx(react.Dropdown, { ...props, children: children });
17196
+ // Nada que envolver (sin hijos o ningún menú suelto): el root tal cual, como el crudo de v3.
17197
+ if (menus.length === 0) {
17198
+ return (jsxRuntime.jsx(react.Dropdown, { ...props, children: mapTree(children, withClonedTrigger) }));
15043
17199
  }
15044
- 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 })] }));
17200
+ return (jsxRuntime.jsxs(react.Dropdown, { ...props, children: [nodes
17201
+ .filter((node) => !isPart$1(node, INSIDE_POPOVER))
17202
+ .map(withClonedTrigger), jsxRuntime.jsx(react.Dropdown.Popover, { offset: 7, placement: "bottom", ...overrides, className: popoverClass, children: menus.map(menuWithClose) })] }));
15045
17203
  };
15046
17204
  DropdownFacade.displayName = "Aurora.Dropdown";
15047
17205
  /**
@@ -15194,21 +17352,66 @@ function hasIndicator(children) {
15194
17352
  return node.type === $8WNJc$react.Fragment && hasIndicator(node.props.children);
15195
17353
  });
15196
17354
  }
17355
+ // ---------------------------------------------------------------------------------------------
17356
+ // `size` de v2 → medidas de pestaña de v2
17357
+ // ---------------------------------------------------------------------------------------------
17358
+ /**
17359
+ * Medidas de pestaña por `size`, **copiadas del oráculo** `@heroui/theme@2.4.15`
17360
+ * `dist/components/tabs.js` (slot `tab`, `px-3` en la base y `size.{sm,md,lg}.tab` para la altura y
17361
+ * el cuerpo), traducidas a clases de Tailwind 4 con el MISMO valor. Las `text-tiny` /
17362
+ * `text-small` / `text-medium` de v2 son del plugin `heroui()` y en TW4 no existen
17363
+ * (`default-layout.js:32-40`):
17364
+ *
17365
+ * | size | v2 | aquí | cuerpo / interlineado |
17366
+ * |------|------------------------|----------------|-----------------------|
17367
+ * | sm | `h-7 text-tiny` + px-3 | `h-7 px-3 text-xs` | 0.75rem / 1rem |
17368
+ * | md | `h-8 text-small` + px-3 | `h-8 px-3 text-sm` | 0.875rem / 1.25rem |
17369
+ * | lg | `h-9 text-medium` + px-3 | `h-9 px-3 text-base` | 1rem / 1.5rem |
17370
+ *
17371
+ * Por qué se mapea, cuando `radius` y `color` se ignoran (§9.1): `size` no es estética, es
17372
+ * TAMAÑO, y descartarlo provoca un desbordamiento medido. linda-business embebido en el host v1 con
17373
+ * la `.140`: con `size="sm"` descartado, el texto pasaba de 12 px a 14 px y cada pestaña salía de 6
17374
+ * a 17 px más ancha. El carril medía 352 px en una columna de 287 px y el chevron del scroller
17375
+ * tapaba la última pestaña. smbs tiene 10 `size="sm"`.
17376
+ *
17377
+ * Lo que NO se copia: el peso (v3 pone `font-medium`, v2 no ponía nada) y el radio (`rounded-small`),
17378
+ * que siguen como v3 por §9.1. Tampoco el `py-1` de v2: con altura fija y `items-center` no
17379
+ * cambia nada.
17380
+ *
17381
+ * Las clases van ANTES de `classNames.tab` y del `className` del `Tab` en un `cn`
17382
+ * (tailwind-merge): lo de la app gana. El `h-7 px-2` de linda-business sigue mandando sobre el
17383
+ * `px-3`. Clases literales en el fuente (no construidas), para que Tailwind las genere.
17384
+ */
17385
+ const TAB_SIZE_CLASS = {
17386
+ sm: "h-7 px-3 text-xs",
17387
+ md: "h-8 px-3 text-sm",
17388
+ lg: "h-9 px-3 text-base",
17389
+ };
17390
+ /**
17391
+ * El `size` del root llega a los `Tabs.Tab` de la forma v3 por contexto, porque esas pestañas las
17392
+ * escribe la app dentro de su `Tabs.List` y la fachada no las recorre. Sin `size` el valor es
17393
+ * `undefined` y la pestaña sale byte a byte como antes.
17394
+ */
17395
+ const TabSizeContext = $8WNJc$react.createContext(undefined);
15197
17396
  const TabFacade = ({ children: ownChildren, title,
15198
17397
  // `titleValue` es de v2 y no tiene destino en v3: se consume para que no llegue al DOM.
15199
17398
  titleValue: _titleValue, ...props }) => {
15200
17399
  // En la forma v3 la etiqueta son los `children`; si solo viene el `title` de v2 (un `Tab` con
15201
17400
  // `title` dentro de `Tabs.List`), hace de etiqueta. Con los dos, mandan los `children`.
15202
17401
  const children = ownChildren ?? title;
17402
+ const sizeClass = $8WNJc$react.useContext(TabSizeContext);
17403
+ const tabProps = sizeClass
17404
+ ? { ...props, className: react.cn(sizeClass, props.className) }
17405
+ : props;
15203
17406
  // `children` como función es la forma de render props de react-aria (`TabRenderProps`). No se
15204
17407
  // puede inspeccionar, y menos aún combinar con un hermano —React rechaza una función como
15205
17408
  // hijo junto a un elemento—, así que se pasa tal cual. Un consumidor que la use monta el
15206
17409
  // indicador él (es la vía de escape de este caso). Exposición hoy: 0 usos en los 6 repos.
15207
17410
  if (typeof children === "function") {
15208
- return jsxRuntime.jsx(react.Tabs.Tab, { ...props, children: children });
17411
+ return jsxRuntime.jsx(react.Tabs.Tab, { ...tabProps, children: children });
15209
17412
  }
15210
17413
  if (hasIndicator(children)) {
15211
- return jsxRuntime.jsx(react.Tabs.Tab, { ...props, children: children });
17414
+ return jsxRuntime.jsx(react.Tabs.Tab, { ...tabProps, children: children });
15212
17415
  }
15213
17416
  // El indicador va PRIMERO por legibilidad del marcado, no por efecto: es `position:absolute`
15214
17417
  // con `z-index:-1`, o sea fuera del flujo y detrás del texto, así que el orden no cambia el
@@ -15218,7 +17421,7 @@ titleValue: _titleValue, ...props }) => {
15218
17421
  // `{children}` se pasa SIN normalizar (nada de `Children.toArray`) para no reescribir las
15219
17422
  // keys del consumidor: hacerlo cambiaría la reconciliación de una lista de pestañas
15220
17423
  // condicional. Verificado en el navegador que la anidación no produce aviso de key.
15221
- return (jsxRuntime.jsxs(react.Tabs.Tab, { ...props, children: [jsxRuntime.jsx(react.Tabs.Indicator, {}), children] }));
17424
+ return (jsxRuntime.jsxs(react.Tabs.Tab, { ...tabProps, children: [jsxRuntime.jsx(react.Tabs.Indicator, {}), children] }));
15222
17425
  };
15223
17426
  TabFacade.displayName = "Aurora.Tab";
15224
17427
  /**
@@ -15329,8 +17532,10 @@ function hasPanelContent(children) {
15329
17532
  }
15330
17533
  const TabsFacade = (allProps) => {
15331
17534
  const { children, classNames, className, variant, fullWidth, items, isVertical, placement, orientation, destroyInactiveTabPanel = true,
17535
+ // v2: medidas de pestaña (ver `TAB_SIZE_CLASS`).
17536
+ size,
15332
17537
  // Sin equivalente en v3: se consumen para que no lleguen al DOM (ver README, «Props v2»).
15333
- size: _size, radius: _radius, color: _color, disableAnimation: _disableAnimation, disableCursorAnimation: _disableCursorAnimation, shouldSelectOnPressUp: _shouldSelectOnPressUp, ...rest } = allProps;
17538
+ radius: _radius, color: _color, disableAnimation: _disableAnimation, disableCursorAnimation: _disableCursorAnimation, shouldSelectOnPressUp: _shouldSelectOnPressUp, ...rest } = allProps;
15334
17539
  const v3Variant = toV3Variant(variant);
15335
17540
  const v3Orientation = orientation ??
15336
17541
  (isVertical || placement === "start" || placement === "end"
@@ -15391,7 +17596,7 @@ const TabsFacade = (allProps) => {
15391
17596
  // `.tabs[data-orientation="horizontal"] > .tabs__list-container { align-self: start }` de
15392
17597
  // neutralize, que no hace falta: dentro de una fila el carril ya mide su contenido.
15393
17598
  const orientationAttr = v3Orientation ?? "horizontal";
15394
- 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 },
17599
+ return (jsxRuntime.jsxs(react.Tabs, { ...rootProps, orientation: v3Orientation, variant: v3Variant, children: [jsxRuntime.jsx("div", { className: react.cn("flex", v3Variant === "secondary" && "tabs--secondary", fullWidth && "w-full", classNames?.base, className), "data-orientation": orientationAttr, "data-slot": "tabs-base", children: jsxRuntime.jsx(react.Tabs.ListContainer, { className: react.cn(fullWidth && "w-full"), children: jsxRuntime.jsx(react.Tabs.List, { "aria-describedby": ariaDescribedby, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledby, className: react.cn(classNames?.tabList), children: tabs.map(({ id, title, tabClassName, tabProps }) => ($8WNJc$react.createElement(react.Tabs.Tab, { ...tabProps, className: react.cn(size && TAB_SIZE_CLASS[size], classNames?.tab, tabClassName), id: id, key: id },
15395
17600
  jsxRuntime.jsx(react.Tabs.Indicator, { className: react.cn(classNames?.cursor) }),
15396
17601
  classNames?.tabContent ? (jsxRuntime.jsx("span", { className: classNames.tabContent, children: title })) : (title)))) }) }) }), tabs
15397
17602
  .filter(({ panel }) => hasPanelContent(panel))
@@ -15410,7 +17615,9 @@ const TabsFacade = (allProps) => {
15410
17615
  orientation: v3Orientation,
15411
17616
  variant: v3Variant,
15412
17617
  };
15413
- return jsxRuntime.jsx(TabsV3, { ...v3Props, children: v3Children });
17618
+ const v3Tabs = jsxRuntime.jsx(TabsV3, { ...v3Props, children: v3Children });
17619
+ // Sin `size` no se monta el provider: el árbol queda idéntico al de antes.
17620
+ return size ? (jsxRuntime.jsx(TabSizeContext.Provider, { value: TAB_SIZE_CLASS[size], children: v3Tabs })) : (v3Tabs);
15414
17621
  };
15415
17622
  const TabsV3 = ({ children, ...props }) => {
15416
17623
  const nodes = $8WNJc$react.Children.toArray(children);
@@ -15475,6 +17682,87 @@ const Tabs = Object.assign(TabsFacade, {
15475
17682
  Tab: TabFacade,
15476
17683
  });
15477
17684
 
17685
+ const OMIT = new Set([
17686
+ "role",
17687
+ "tabIndex",
17688
+ "className",
17689
+ "data-slot",
17690
+ "children",
17691
+ ]);
17692
+ function mergeRefs(...refs) {
17693
+ return (value) => {
17694
+ for (const ref of refs) {
17695
+ if (typeof ref === "function") {
17696
+ ref(value);
17697
+ }
17698
+ else if (ref) {
17699
+ ref.current = value;
17700
+ }
17701
+ }
17702
+ };
17703
+ }
17704
+ /** `mergeProps` de v2: handlers `on*` encadenados (trigger → hijo), el resto gana el hijo. */
17705
+ function mergeTriggerProps(trigger, child) {
17706
+ const merged = { ...trigger, ...child };
17707
+ for (const key of Object.keys(trigger)) {
17708
+ const a = trigger[key];
17709
+ const b = child[key];
17710
+ if (/^on[A-Z]/.test(key) &&
17711
+ typeof a === "function" &&
17712
+ typeof b === "function") {
17713
+ merged[key] = (...args) => {
17714
+ a(...args);
17715
+ b(...args);
17716
+ };
17717
+ }
17718
+ else if (key === "aria-describedby" && a && b) {
17719
+ merged[key] = `${a} ${b}`;
17720
+ }
17721
+ else if (b === undefined) {
17722
+ merged[key] = a;
17723
+ }
17724
+ }
17725
+ return merged;
17726
+ }
17727
+ function renderTooltipTrigger(children) {
17728
+ return (divProps) => {
17729
+ const { ref, ...rest } = divProps;
17730
+ const triggerProps = {};
17731
+ for (const [key, value] of Object.entries(rest)) {
17732
+ if (!OMIT.has(key)) {
17733
+ triggerProps[key] = value;
17734
+ }
17735
+ }
17736
+ const single = $8WNJc$react.Children.count(children) === 1 && $8WNJc$react.isValidElement(children);
17737
+ if (single && children.type !== $8WNJc$react.Fragment) {
17738
+ const child = children;
17739
+ const childRef = (Number.parseInt($8WNJc$react.version, 10) < 19
17740
+ ? child.ref
17741
+ : child.props.ref);
17742
+ return $8WNJc$react.cloneElement(child, {
17743
+ ...mergeTriggerProps(triggerProps, child.props),
17744
+ ref: mergeRefs(childRef, ref),
17745
+ });
17746
+ }
17747
+ return (jsxRuntime.jsx("div", { ...triggerProps, ref: ref, className: rest.className, "data-slot": "tooltip-trigger", children: children }));
17748
+ };
17749
+ }
17750
+
17751
+ /**
17752
+ * `color` de v2 (`@heroui/theme@2.4.15`, `popover` → `colorVariants.solid`): fondo y texto del
17753
+ * color en la caja, y la flecha del mismo color (v2 la pintaba con `before:bg-*`; en v3 es un SVG
17754
+ * con `fill: var(--overlay)`, así que se pisa su `fill`). `default` no añade nada: se ve como v3
17755
+ * (§9.1). Clases LITERALES — Tailwind 4 solo genera lo que está escrito en el fuente.
17756
+ */
17757
+ const COLOR_CLASSES = {
17758
+ default: "",
17759
+ foreground: "bg-foreground text-background [&_[data-slot=overlay-arrow]]:fill-foreground",
17760
+ primary: "bg-primary text-primary-foreground [&_[data-slot=overlay-arrow]]:fill-primary",
17761
+ secondary: "bg-secondary text-secondary-foreground [&_[data-slot=overlay-arrow]]:fill-secondary",
17762
+ success: "bg-success text-success-foreground [&_[data-slot=overlay-arrow]]:fill-success",
17763
+ warning: "bg-warning text-warning-foreground [&_[data-slot=overlay-arrow]]:fill-warning",
17764
+ danger: "bg-danger text-danger-foreground [&_[data-slot=overlay-arrow]]:fill-danger",
17765
+ };
15478
17766
  /**
15479
17767
  * Tooltip - Componente BeweOS basado en HeroUI.
15480
17768
  *
@@ -15490,7 +17778,11 @@ const Tabs = Object.assign(TabsFacade, {
15490
17778
  const Tooltip = ({ enableTapOnTouch = true, touchPlacement = "top", placement, offset, showArrow = false, content, isOpen, defaultOpen, onOpenChange, isDisabled,
15491
17779
  // v3 cambia el default de 0 (v2) a 700 — se mantiene 0 para no regresionar el
15492
17780
  // comportamiento instantáneo que ya tenía v2 (ver Tooltip.types.ts).
15493
- delay = 0, closeDelay, trigger, className, children, }) => {
17781
+ delay = 0, closeDelay, trigger, className, classNames, color = "default", children, }) => {
17782
+ // v3 tiene un solo nodo donde v2 tenía `base` + `content` (ver `TooltipClassNames`). Orden de
17783
+ // v2: color → `classNames` → `className`; `cn` fusiona para que la clase de la app gane.
17784
+ const contentClassName = react.cn(COLOR_CLASSES[color] ?? "", classNames?.base, classNames?.content, className);
17785
+ const arrow = showArrow && (jsxRuntime.jsx(react.Tooltip.Arrow, { className: classNames?.arrow }));
15494
17786
  // Detección táctil: NO basta con `(hover: none)`. Muchos móviles (p.ej.
15495
17787
  // Android) reportan `(hover: hover)` y `(hover: none)` da false, dejando el
15496
17788
  // tap sin activar. Se combina con `(pointer: coarse)` (coincide si cualquiera
@@ -15528,7 +17820,7 @@ delay = 0, closeDelay, trigger, className, children, }) => {
15528
17820
  }, [tapMode, open, setOpen]);
15529
17821
  // Desktop (o tap deshabilitado) → comportamiento nativo de HeroUI.
15530
17822
  if (!tapMode) {
15531
- 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] })] }));
17823
+ 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] })] }));
15532
17824
  }
15533
17825
  // Táctil → controlado, el tap sobre el trigger alterna la visibilidad.
15534
17826
  // El placement cambia a `touchPlacement` (arriba por defecto) para que no
@@ -15540,7 +17832,7 @@ delay = 0, closeDelay, trigger, className, children, }) => {
15540
17832
  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) => {
15541
17833
  event.stopPropagation();
15542
17834
  setOpen(!open);
15543
- }, children: children }) }), jsxRuntime.jsxs(react.Tooltip.Content, { placement: touchPlacement, offset: offset, showArrow: showArrow, className: className, children: [showArrow && jsxRuntime.jsx(react.Tooltip.Arrow, {}), content] })] }));
17835
+ }, children: children }) }), jsxRuntime.jsxs(react.Tooltip.Content, { placement: touchPlacement, offset: offset, showArrow: showArrow, className: contentClassName, children: [arrow, content] })] }));
15544
17836
  };
15545
17837
 
15546
17838
  /**