@beweco/aurora-ui 1.0.0-beta.139 → 1.0.0-beta.140
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/css/styles.css +23 -1
- package/dist/index.cjs.js +2172 -352
- package/dist/index.esm.js +2203 -383
- package/dist/types/compat/heroui-v2-shims.d.ts +143 -30
- package/dist/types/compat/heroui-v2-shims.d.ts.map +1 -1
- package/dist/types/components/autocomplete/AutoComplete.d.ts.map +1 -1
- package/dist/types/components/autocomplete/AutoComplete.types.d.ts +36 -2
- package/dist/types/components/autocomplete/AutoComplete.types.d.ts.map +1 -1
- package/dist/types/components/autocomplete/index.d.ts +1 -1
- package/dist/types/components/autocomplete/index.d.ts.map +1 -1
- package/dist/types/components/button/Button.d.ts +3 -0
- package/dist/types/components/button/Button.d.ts.map +1 -1
- package/dist/types/components/button/Button.types.d.ts +24 -1
- package/dist/types/components/button/Button.types.d.ts.map +1 -1
- package/dist/types/components/button/_internal/press-event.d.ts +16 -0
- package/dist/types/components/button/_internal/press-event.d.ts.map +1 -0
- package/dist/types/components/button/index.d.ts +1 -1
- package/dist/types/components/button/index.d.ts.map +1 -1
- package/dist/types/components/card/Card.d.ts +13 -1
- package/dist/types/components/card/Card.d.ts.map +1 -1
- package/dist/types/components/card/Card.types.d.ts +53 -1
- package/dist/types/components/card/Card.types.d.ts.map +1 -1
- package/dist/types/components/card/index.d.ts +1 -1
- package/dist/types/components/card/index.d.ts.map +1 -1
- package/dist/types/components/chip/Chip.d.ts +1 -1
- package/dist/types/components/chip/Chip.d.ts.map +1 -1
- package/dist/types/components/chip/Chip.types.d.ts +28 -6
- package/dist/types/components/chip/Chip.types.d.ts.map +1 -1
- package/dist/types/components/datepicker/DatePicker.d.ts +1 -1
- package/dist/types/components/datepicker/DatePicker.d.ts.map +1 -1
- package/dist/types/components/datepicker/DatePicker.types.d.ts +15 -0
- package/dist/types/components/datepicker/DatePicker.types.d.ts.map +1 -1
- package/dist/types/components/datepicker/index.d.ts +1 -1
- package/dist/types/components/datepicker/index.d.ts.map +1 -1
- package/dist/types/components/dropdown/Dropdown.d.ts +4 -2
- package/dist/types/components/dropdown/Dropdown.d.ts.map +1 -1
- package/dist/types/components/dropdown/Dropdown.types.d.ts +61 -5
- package/dist/types/components/dropdown/Dropdown.types.d.ts.map +1 -1
- package/dist/types/components/dropdown/_internal/renderTrigger.d.ts +47 -0
- package/dist/types/components/dropdown/_internal/renderTrigger.d.ts.map +1 -0
- package/dist/types/components/dropdown/index.d.ts +1 -1
- package/dist/types/components/dropdown/index.d.ts.map +1 -1
- package/dist/types/components/input/Input.d.ts +4 -0
- package/dist/types/components/input/Input.d.ts.map +1 -1
- package/dist/types/components/input/Input.types.d.ts +37 -1
- package/dist/types/components/input/Input.types.d.ts.map +1 -1
- package/dist/types/components/modal/Modal.d.ts +3 -2
- package/dist/types/components/modal/Modal.d.ts.map +1 -1
- package/dist/types/components/modal/Modal.types.d.ts +51 -3
- package/dist/types/components/modal/Modal.types.d.ts.map +1 -1
- package/dist/types/components/modal/ModalFacadeContext.d.ts +27 -0
- package/dist/types/components/modal/ModalFacadeContext.d.ts.map +1 -0
- package/dist/types/components/modal/_internal/ModalBody.d.ts +1 -1
- package/dist/types/components/modal/_internal/ModalBody.d.ts.map +1 -1
- package/dist/types/components/modal/_internal/ModalContent.d.ts +10 -5
- package/dist/types/components/modal/_internal/ModalContent.d.ts.map +1 -1
- package/dist/types/components/modal/_internal/ModalFooter.d.ts +1 -1
- package/dist/types/components/modal/_internal/ModalFooter.d.ts.map +1 -1
- package/dist/types/components/modal/_internal/ModalHeader.d.ts +4 -2
- package/dist/types/components/modal/_internal/ModalHeader.d.ts.map +1 -1
- package/dist/types/components/pagination/Pagination.d.ts +10 -0
- package/dist/types/components/pagination/Pagination.d.ts.map +1 -1
- package/dist/types/components/pagination/Pagination.types.d.ts +21 -0
- package/dist/types/components/pagination/Pagination.types.d.ts.map +1 -1
- package/dist/types/components/select/Select.d.ts.map +1 -1
- package/dist/types/components/select/Select.types.d.ts +64 -0
- package/dist/types/components/select/Select.types.d.ts.map +1 -1
- package/dist/types/components/select/_internal/v2-collection.d.ts +64 -0
- package/dist/types/components/select/_internal/v2-collection.d.ts.map +1 -0
- package/dist/types/components/select/index.d.ts +1 -1
- package/dist/types/components/select/index.d.ts.map +1 -1
- package/dist/types/components/table/AuraTable.types.d.ts +5 -3
- package/dist/types/components/table/AuraTable.types.d.ts.map +1 -1
- package/dist/types/components/table/Table.d.ts +15 -5
- package/dist/types/components/table/Table.d.ts.map +1 -1
- package/dist/types/components/table/TableContent.d.ts +32 -0
- package/dist/types/components/table/TableContent.d.ts.map +1 -0
- package/dist/types/components/table/_internal/v2-compat.d.ts +94 -0
- package/dist/types/components/table/_internal/v2-compat.d.ts.map +1 -0
- package/dist/types/components/textarea/Textarea.d.ts.map +1 -1
- package/dist/types/components/textarea/Textarea.types.d.ts +13 -0
- package/dist/types/components/textarea/Textarea.types.d.ts.map +1 -1
- package/dist/types/components/textarea/useTextareaAutosize.d.ts +13 -0
- package/dist/types/components/textarea/useTextareaAutosize.d.ts.map +1 -0
- package/dist/types/components/time-input/TimeInput.d.ts.map +1 -1
- package/dist/types/components/time-input/TimeInput.types.d.ts +13 -1
- package/dist/types/components/time-input/TimeInput.types.d.ts.map +1 -1
- package/dist/types/components/tooltip/Tooltip.d.ts.map +1 -1
- package/dist/types/components/tooltip/Tooltip.types.d.ts +31 -1
- package/dist/types/components/tooltip/Tooltip.types.d.ts.map +1 -1
- package/dist/types/components/tooltip/_internal/renderTrigger.d.ts +3 -0
- package/dist/types/components/tooltip/_internal/renderTrigger.d.ts.map +1 -0
- package/dist/types/components/tooltip/index.d.ts +1 -1
- package/dist/types/components/tooltip/index.d.ts.map +1 -1
- package/dist/types/index.d.ts +6 -6
- package/dist/types/index.d.ts.map +1 -1
- 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
|
-
|
|
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:
|
|
65
|
-
*
|
|
66
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
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,21 @@ 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
|
+
};
|
|
763
|
+
// `variant="shadow"` de v2, literal de `@heroui/theme@2.4.15` (`dist/utils/variants.js`,
|
|
764
|
+
// `colorVariants.shadow`): `shadow-lg shadow-<color>/40 bg-<color> text-<color>-foreground`
|
|
765
|
+
// (`default` usa `/50`). El relleno y el texto son los de `solid`; aquí solo se añade la sombra.
|
|
766
|
+
// Clases literales (no plantillas) para que Tailwind las genere al escanear `src/`. Los 5 colores
|
|
767
|
+
// fijos de Aurora (red/purple/…) no existían en v2 con `shadow`: reciben la sombra neutra.
|
|
768
|
+
const SHADOW_CLASS$1 = {
|
|
769
|
+
default: "shadow-lg shadow-default/50",
|
|
770
|
+
primary: "shadow-lg shadow-primary/40",
|
|
771
|
+
secondary: "shadow-lg shadow-secondary/40",
|
|
772
|
+
success: "shadow-lg shadow-success/40",
|
|
773
|
+
warning: "shadow-lg shadow-warning/40",
|
|
774
|
+
danger: "shadow-lg shadow-danger/40",
|
|
500
775
|
};
|
|
501
776
|
// TECH-2459 · F5 · P-08c — Tamaños de v2 (`chip.ts`): la altura era FIJA y el texto más grande que
|
|
502
777
|
// en v3, y el sangrado horizontal salía de dos slots (`base` + `content`):
|
|
@@ -734,7 +1009,7 @@ function colorStyle(color, shape) {
|
|
|
734
1009
|
* y el hueco que queda tras el botón de cierre sean distintos, como en v2 (ver `SIZE_CLASS` y
|
|
735
1010
|
* `CONTENT_PADDING`).
|
|
736
1011
|
*/
|
|
737
|
-
const Chip = ({ color = "default", variant = "solid", size = "md", radius = "full", startContent, endContent, onClose, isDisabled = false, className, classNames, children,
|
|
1012
|
+
const Chip = ({ color = "default", variant = "solid", size = "md", radius = "full", startContent, endContent, onClose, isDisabled = false, className, classNames, children, as, style: consumerStyle, ...domProps }) => {
|
|
738
1013
|
const isStandard = STANDARD_COLORS.has(color);
|
|
739
1014
|
const shape = SHAPE_BY_VARIANT[variant];
|
|
740
1015
|
// Literal de v2 (`@heroui/chip@2.2.18`, `use-chip.js:63`):
|
|
@@ -758,12 +1033,27 @@ const Chip = ({ color = "default", variant = "solid", size = "md", radius = "ful
|
|
|
758
1033
|
const flatDarkFgClass = isStandard && shape === "soft"
|
|
759
1034
|
? (COLOR_TOKENS[color].flatFgDarkClass ?? "")
|
|
760
1035
|
: "";
|
|
761
|
-
const
|
|
1036
|
+
const baseStyle = vars
|
|
762
1037
|
? shape === "outline"
|
|
763
1038
|
? { ...vars, borderColor: vars["--chip-fg"] }
|
|
764
1039
|
: vars
|
|
765
1040
|
: undefined;
|
|
766
|
-
|
|
1041
|
+
// El `style` del consumidor va DESPUÉS: pisa el relleno/texto calculados (uso real en
|
|
1042
|
+
// beweossmbs: `style={{ backgroundColor, color }}` con el color del agente, 5 sitios).
|
|
1043
|
+
const style = baseStyle || consumerStyle ? { ...baseStyle, ...consumerStyle } : undefined;
|
|
1044
|
+
const shadowClass = variant === "shadow"
|
|
1045
|
+
? isStandard
|
|
1046
|
+
? SHADOW_CLASS$1[color]
|
|
1047
|
+
: "shadow-lg"
|
|
1048
|
+
: "";
|
|
1049
|
+
// `as` de v2 (`Component = as || "div"`). v3 no tiene `as`, pero su raíz acepta `render`, que
|
|
1050
|
+
// recibe las props DOM ya compuestas (clases, `data-slot`, `ref`…) y devuelve el elemento.
|
|
1051
|
+
const render = as
|
|
1052
|
+
? (props) => $8WNJc$react.createElement(as, props)
|
|
1053
|
+
: undefined;
|
|
1054
|
+
return (jsxRuntime.jsxs(react.Chip
|
|
1055
|
+
// `aria-disabled` ANTES del spread: si el consumidor manda el suyo, gana el suyo.
|
|
1056
|
+
, { "aria-disabled": isDisabled || undefined, ...domProps, render: render, size: size, variant: V3_VARIANT_BY_SHAPE[shape],
|
|
767
1057
|
// `cn` (tailwind-merge) y no una concatenación de texto: el consumidor tiene que poder
|
|
768
1058
|
// pisar tamaño y radio (`className="h-5 text-[10px]"` es un uso real en beweossmbs).
|
|
769
1059
|
// Con una cadena suelta ambas clases llegan al DOM y gana la que Tailwind emita más
|
|
@@ -772,18 +1062,20 @@ const Chip = ({ color = "default", variant = "solid", size = "md", radius = "ful
|
|
|
772
1062
|
className: react.cn(SIZE_CLASS$6[size],
|
|
773
1063
|
// Después de `SIZE_CLASS` a propósito: el cuadrado de un carácter ENCOGE la caja
|
|
774
1064
|
// 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,
|
|
1065
|
+
isSquare ? ONE_CHAR_CLASS[size] : "", RADIUS_CLASS$9[radius], outlineClass, shadowClass, flatDarkFgClass, customClass, isDisabled ? "opacity-50 pointer-events-none" : "", classNames?.base, className), style: style, children: [startContent, jsxRuntime.jsx("span", { className: react.cn("flex-1 text-inherit font-normal", startContent
|
|
776
1066
|
? CONTENT_PADDING[size].leftWithContent
|
|
777
1067
|
: CONTENT_PADDING[size].left, hasEndContent
|
|
778
1068
|
? CONTENT_PADDING[size].rightWithContent
|
|
779
1069
|
: CONTENT_PADDING[size].right,
|
|
780
1070
|
// `content: "px-0 flex-none"` del compound de un carácter. Va después de los
|
|
781
1071
|
// `pl-*`/`pr-*` para que tailwind-merge los borre (un `px` posterior sí gana).
|
|
782
|
-
isSquare ? "px-0 flex-none" : "", classNames?.label
|
|
1072
|
+
isSquare ? "px-0 flex-none" : "", classNames?.label,
|
|
1073
|
+
// `content` es el nombre v2 del slot (16 usos en beweossmbs); va el último.
|
|
1074
|
+
classNames?.content), children: children }), endContent, onClose && (jsxRuntime.jsx(react.CloseButton, { "aria-label": "Cerrar",
|
|
783
1075
|
// `.close-button` de v3 impone caja de 24px, `bg-default` y `text-muted`; v2 no
|
|
784
1076
|
// pintaba fondo ni caja: el glifo ERA el botón. Se anulan con utilidades (capa
|
|
785
1077
|
// `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" }) }) }))] }));
|
|
1078
|
+
className: react.cn("h-auto w-auto min-h-0 rounded-full bg-transparent p-0 text-current opacity-70 transition-opacity hover:bg-transparent hover:opacity-100", classNames?.closeButton), onPress: onClose, isDisabled: isDisabled, children: jsxRuntime.jsx("svg", { "aria-hidden": "true", focusable: "false", role: "presentation", viewBox: "0 0 24 24", className: `m-0 shrink-0 ${CLOSE_ICON_SIZE_CLASS[size]}`, children: jsxRuntime.jsx("path", { d: CLOSE_FILLED_PATH, fill: "currentColor" }) }) }))] }));
|
|
787
1079
|
};
|
|
788
1080
|
|
|
789
1081
|
const sizeMap = {
|
|
@@ -824,10 +1116,10 @@ const IconComponent = ({ icon, size = "md", className, style, color, hFlip, vFli
|
|
|
824
1116
|
// (`bg-default-200` / `bg-default-300` en variant `flat`) se reponen también por clase por el
|
|
825
1117
|
// mismo motivo.
|
|
826
1118
|
//
|
|
827
|
-
// El RADIO no entra aquí a propósito: se compone por pieza más abajo.
|
|
828
|
-
//
|
|
829
|
-
//
|
|
830
|
-
// `rounded-[12px]` y la celda activa salía a escuadra.
|
|
1119
|
+
// El RADIO no entra aquí a propósito: se compone por pieza más abajo (`cellRadius`). Las piezas se
|
|
1120
|
+
// fusionan con `cn` (tailwind-merge), que resuelve dos clases del mismo grupo por su POSICIÓN en
|
|
1121
|
+
// la cadena. Antes se concatenaban como texto y decidía el orden en que Tailwind las emite:
|
|
1122
|
+
// `rounded-none` ganaba a `rounded-[12px]` y la celda activa salía a escuadra.
|
|
831
1123
|
// ⚠️ TECH-2459 · F5 · R2 — `shadow-xs`, no `shadow-sm`: **Tailwind 4 corrió la escala de sombras
|
|
832
1124
|
// un paso**. El `shadow-sm` de Tailwind 3 (que es el que emitía v2) es el `shadow-xs` de Tailwind
|
|
833
1125
|
// 4. Literal de `node_modules/tailwindcss/theme.css:406-407`:
|
|
@@ -852,11 +1144,38 @@ const CELL_CLASS = "size-9 md:size-9 min-w-9 bg-default-100 text-sm text-default
|
|
|
852
1144
|
* `w-fit` reproduce el `max-w-fit` del wrapper de v2.
|
|
853
1145
|
*/
|
|
854
1146
|
const STRIP_CLASS = "w-fit gap-0 rounded-[12px] shadow-xs";
|
|
855
|
-
/**
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
1147
|
+
/**
|
|
1148
|
+
* `isCompact={false}`: celdas sueltas. Literales del `tv` de v2 sin el bloque `isCompact`: el
|
|
1149
|
+
* `wrapper` vuelve a `gap-1` (4px) sin sombra común, y cada celda lleva su propio radio
|
|
1150
|
+
* (`radius: md` → `rounded-medium`, 12px).
|
|
1151
|
+
*/
|
|
1152
|
+
const STRIP_LOOSE_CLASS = "w-fit gap-1";
|
|
1153
|
+
/** Controles anterior/siguiente: celda cuadrada, sin el padding lateral de v3. */
|
|
1154
|
+
const NAV_CLASS = "w-9 px-0";
|
|
1155
|
+
/**
|
|
1156
|
+
* Radio de cada pieza según su posición en la tira.
|
|
1157
|
+
*
|
|
1158
|
+
* Compacta (el `isCompact` de v2): celdas interiores a escuadra
|
|
1159
|
+
* (`[&:not(:first-of-type):not(:last-of-type)]:rounded-none`) y solo el lado EXTERIOR de los
|
|
1160
|
+
* extremos redondeado (`first-of-type:rounded-e-none` / `last-of-type:rounded-s-none`). Los
|
|
1161
|
+
* extremos son las flechas cuando hay `showControls`, y las propias páginas cuando no: por eso
|
|
1162
|
+
* se decide por posición y no por tipo de pieza.
|
|
1163
|
+
*/
|
|
1164
|
+
function cellRadius(isCompact, position) {
|
|
1165
|
+
if (!isCompact) {
|
|
1166
|
+
return "rounded-[12px]";
|
|
1167
|
+
}
|
|
1168
|
+
if (position.isFirst && position.isLast) {
|
|
1169
|
+
return "rounded-[12px]";
|
|
1170
|
+
}
|
|
1171
|
+
if (position.isFirst) {
|
|
1172
|
+
return "rounded-s-[12px] rounded-e-none";
|
|
1173
|
+
}
|
|
1174
|
+
if (position.isLast) {
|
|
1175
|
+
return "rounded-e-[12px] rounded-s-none";
|
|
1176
|
+
}
|
|
1177
|
+
return "rounded-none";
|
|
1178
|
+
}
|
|
860
1179
|
/**
|
|
861
1180
|
* Página activa. v2 la pintaba con un "cursor" absoluto de `bg-primary text-primary-foreground` y
|
|
862
1181
|
* `rounded-medium` por encima de la celda; aquí se pinta la celda misma con los mismos tokens.
|
|
@@ -864,7 +1183,39 @@ const NAV_END_CLASS = `${CELL_CLASS} w-9 px-0 rounded-e-[12px] rounded-s-none`;
|
|
|
864
1183
|
* Sin esto v3 la pinta con `--pagination-link-bg: var(--default)`, el gris CRUDO de v3 y no el
|
|
865
1184
|
* `--color-default` de Aurora (falso amigo, decisiones-aceptadas-f1.md D-F1-3).
|
|
866
1185
|
*/
|
|
867
|
-
const ACTIVE_LINK_CLASS = "rounded-[12px]
|
|
1186
|
+
const ACTIVE_LINK_CLASS = "rounded-[12px]";
|
|
1187
|
+
/**
|
|
1188
|
+
* `color` de v2 → la página activa. Literal de `colorVariants.solid` (`@heroui/theme@2.4.15`,
|
|
1189
|
+
* `dist/components/pagination.js`): `bg-<color> text-<color>-foreground`. Los estados hover y
|
|
1190
|
+
* pulsado se fijan al mismo fondo porque la celda base los cambia (`CELL_CLASS`) y en v2 el
|
|
1191
|
+
* cursor activo no reaccionaba al ratón. Cadenas literales completas: Tailwind solo emite lo que
|
|
1192
|
+
* encuentra escrito entero.
|
|
1193
|
+
*
|
|
1194
|
+
* `default` es el gris de Aurora (`--color-default`), no el `--default` crudo de v3 (falso
|
|
1195
|
+
* amigo, decisiones-aceptadas-f1.md D-F1-3).
|
|
1196
|
+
*/
|
|
1197
|
+
const ACTIVE_COLOR_CLASS = {
|
|
1198
|
+
default: "bg-default text-default-foreground hover:bg-default active:bg-default",
|
|
1199
|
+
primary: "bg-primary text-primary-foreground hover:bg-primary active:bg-primary",
|
|
1200
|
+
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary active:bg-secondary",
|
|
1201
|
+
success: "bg-success text-success-foreground hover:bg-success active:bg-success",
|
|
1202
|
+
warning: "bg-warning text-warning-foreground hover:bg-warning active:bg-warning",
|
|
1203
|
+
danger: "bg-danger text-danger-foreground hover:bg-danger active:bg-danger",
|
|
1204
|
+
};
|
|
1205
|
+
/**
|
|
1206
|
+
* `showShadow` de v2 → sombra de color bajo la página activa. Literal del compound
|
|
1207
|
+
* `{ showShadow: true, color }` de v2: `cursor: [colorVariants.shadow[color], "shadow-md"]`, que
|
|
1208
|
+
* tras tailwind-merge queda `shadow-md shadow-<color>/40` (`/50` en `default`). `shadow-md` no
|
|
1209
|
+
* cambió de valor entre Tailwind 3 y 4 (el corrimiento de la escala solo afectó a `sm`/`DEFAULT`).
|
|
1210
|
+
*/
|
|
1211
|
+
const ACTIVE_SHADOW_CLASS = {
|
|
1212
|
+
default: "shadow-md shadow-default/50",
|
|
1213
|
+
primary: "shadow-md shadow-primary/40",
|
|
1214
|
+
secondary: "shadow-md shadow-secondary/40",
|
|
1215
|
+
success: "shadow-md shadow-success/40",
|
|
1216
|
+
warning: "shadow-md shadow-warning/40",
|
|
1217
|
+
danger: "shadow-md shadow-danger/40",
|
|
1218
|
+
};
|
|
868
1219
|
/**
|
|
869
1220
|
* Traducciones por defecto en español. Todas son `aria-label`: este componente no tiene más
|
|
870
1221
|
* texto visible que los números de página.
|
|
@@ -976,12 +1327,22 @@ function getPaginationRange(total, current) {
|
|
|
976
1327
|
* `aria-label` los dos botones llegaban vacíos al árbol de accesibilidad.
|
|
977
1328
|
*
|
|
978
1329
|
|
|
1330
|
+
* API v2 que las apps siguen escribiendo (medido en beweossmbs `origin/staging`: 17 usos, todos
|
|
1331
|
+
* con `showControls`, 14 con `isCompact`, 12 con `color="primary"`, 11 con `showShadow`):
|
|
1332
|
+
* - `showControls` (default `true`): pinta anterior/siguiente; con `false` desaparecen.
|
|
1333
|
+
* - `isCompact` (default `true`): tira compacta; con `false`, celdas sueltas (`gap-1`).
|
|
1334
|
+
* - `color` (default `"primary"`): color de la página activa.
|
|
1335
|
+
* - `showShadow` (default `false`): sombra de color bajo la página activa.
|
|
1336
|
+
* Los defaults `true` de las dos primeras son los que forzaba la fachada v2 de Aurora
|
|
1337
|
+
* (`<HeroUIPagination showControls isCompact {...props} />`), así que quien no las pasa se ve
|
|
1338
|
+
* exactamente igual que hasta ahora.
|
|
1339
|
+
*
|
|
979
1340
|
* ⚠️ `size` se acepta pero no se aplica: mismo bug preexistente que v2 (destructurado, nunca
|
|
980
1341
|
* reenviado a HeroUI) — fuera de scope de esta migración, reportado aparte (ver
|
|
981
1342
|
* findings/decisiones-aceptadas-g2.md y f5-L7-sueltos.md · P-17). v3 SÍ tiene la prop (emite
|
|
982
1343
|
* `.pagination--sm|--md|--lg`), así que arreglarlo es un cambio de tamaño visible: ticket aparte.
|
|
983
1344
|
*/
|
|
984
|
-
const Pagination = ({ total, page, initialPage = 1, onChange, size: _size = "sm", className, isDisabled, translations = {}, }) => {
|
|
1345
|
+
const Pagination = ({ total, page, initialPage = 1, onChange, size: _size = "sm", className, isDisabled, showControls = true, isCompact = true, showShadow = false, color = "primary", translations = {}, }) => {
|
|
985
1346
|
const [internalPage, setInternalPage] = $8WNJc$react.useState(initialPage);
|
|
986
1347
|
const currentPage = page ?? internalPage;
|
|
987
1348
|
const t = { ...DEFAULT_TRANSLATIONS$g, ...translations };
|
|
@@ -998,18 +1359,26 @@ const Pagination = ({ total, page, initialPage = 1, onChange, size: _size = "sm"
|
|
|
998
1359
|
// `dist/pagination.js:464`). Si `page` viene fuera de rango, `indexOf` da -1 y todas las
|
|
999
1360
|
// elipsis avanzan — también igual que v2, que no protegía ese caso.
|
|
1000
1361
|
const activeIndex = items.indexOf(currentPage);
|
|
1362
|
+
// Posición de cada pieza en la tira, para el radio compacto: con controles, los extremos
|
|
1363
|
+
// son las flechas y ninguna página toca el borde.
|
|
1364
|
+
const lastItemIndex = items.length - 1;
|
|
1365
|
+
const itemPosition = (index) => ({
|
|
1366
|
+
isFirst: !showControls && index === 0,
|
|
1367
|
+
isLast: !showControls && index === lastItemIndex,
|
|
1368
|
+
});
|
|
1369
|
+
const activeClass = react.cn(ACTIVE_COLOR_CLASS[color], showShadow ? ACTIVE_SHADOW_CLASS[color] : "", ACTIVE_LINK_CLASS);
|
|
1001
1370
|
return (
|
|
1002
1371
|
// `w-fit` reproduce el `max-w-fit` del wrapper de v2: `.pagination` de v3 es `w-full` +
|
|
1003
1372
|
// `justify-between`, así que dentro de un `flex justify-center` (como el de AccordionList) la
|
|
1004
1373
|
// tira se quedaba pegada a la izquierda en vez de centrarse.
|
|
1005
|
-
jsxRuntime.jsx(react.Pagination, { className:
|
|
1374
|
+
jsxRuntime.jsx(react.Pagination, { className: react.cn("w-fit", className), children: jsxRuntime.jsxs(react.Pagination.Content, { className: isCompact ? STRIP_CLASS : STRIP_LOOSE_CLASS, children: [showControls && (jsxRuntime.jsx(react.Pagination.Item, { children: jsxRuntime.jsx(react.Pagination.Previous
|
|
1006
1375
|
// v3 mete el galón en un `<span aria-hidden="true">`
|
|
1007
1376
|
// (`pagination.js:165`), así que sin esto el botón se queda SIN nombre
|
|
1008
1377
|
// accesible: un lector de pantalla lo anuncia como "botón" a secas. v2 sí
|
|
1009
1378
|
// lo nombraba ("previous page button", `use-pagination.js:213`), pero en
|
|
1010
1379
|
// inglés y hardcodeado; aquí va por `translations` (regla obligatoria de
|
|
1011
1380
|
// i18n del repo, skill `auraui-i18n`).
|
|
1012
|
-
, { "aria-label": t.previousPageAriaLabel, className:
|
|
1381
|
+
, { "aria-label": t.previousPageAriaLabel, className: react.cn(CELL_CLASS, NAV_CLASS, cellRadius(isCompact, { isFirst: true, isLast: false })), isDisabled: isDisabled || currentPage <= 1, onPress: () => goTo(currentPage - 1), children: jsxRuntime.jsx(react.Pagination.PreviousIcon, {}) }) })), items.map((item, index) => {
|
|
1013
1382
|
if (item === "ellipsis") {
|
|
1014
1383
|
const isBefore = index < activeIndex;
|
|
1015
1384
|
const label = isBefore
|
|
@@ -1017,14 +1386,14 @@ const Pagination = ({ total, page, initialPage = 1, onChange, size: _size = "sm"
|
|
|
1017
1386
|
: t.jumpForwardAriaLabel;
|
|
1018
1387
|
return (
|
|
1019
1388
|
// 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:
|
|
1389
|
+
jsxRuntime.jsx(react.Pagination.Item, { children: jsxRuntime.jsxs(react.Pagination.Link, { "aria-label": label.replace("{count}", String(DOTS_JUMP)), className: react.cn("group", CELL_CLASS, cellRadius(isCompact, itemPosition(index))), isDisabled: isDisabled, onPress: () => goTo(isBefore
|
|
1021
1390
|
? currentPage - DOTS_JUMP
|
|
1022
1391
|
: currentPage + DOTS_JUMP), children: [jsxRuntime.jsx(EllipsisDotsIcon, {}), jsxRuntime.jsx(JumpIcon, { isBefore: isBefore })] }) }, `ellipsis-${index}`));
|
|
1023
1392
|
}
|
|
1024
|
-
return (jsxRuntime.jsx(react.Pagination.Item, { children: jsxRuntime.jsx(react.Pagination.Link, { className:
|
|
1025
|
-
}), jsxRuntime.jsx(react.Pagination.Item, { children: jsxRuntime.jsx(react.Pagination.Next
|
|
1393
|
+
return (jsxRuntime.jsx(react.Pagination.Item, { children: jsxRuntime.jsx(react.Pagination.Link, { className: react.cn(CELL_CLASS, cellRadius(isCompact, itemPosition(index)), item === currentPage ? activeClass : ""), isActive: item === currentPage, isDisabled: isDisabled, onPress: () => goTo(item), children: item }) }, item));
|
|
1394
|
+
}), showControls && (jsxRuntime.jsx(react.Pagination.Item, { children: jsxRuntime.jsx(react.Pagination.Next
|
|
1026
1395
|
/* Mismo motivo que en `Previous`: el galón es `aria-hidden`. */
|
|
1027
|
-
, { "aria-label": t.nextPageAriaLabel, className:
|
|
1396
|
+
, { "aria-label": t.nextPageAriaLabel, className: react.cn(CELL_CLASS, NAV_CLASS, cellRadius(isCompact, { isFirst: false, isLast: true })), isDisabled: isDisabled || currentPage >= total, onPress: () => goTo(currentPage + 1), children: jsxRuntime.jsx(react.Pagination.NextIcon, {}) }) }))] }) }));
|
|
1028
1397
|
};
|
|
1029
1398
|
|
|
1030
1399
|
// TECH-2459 · F5 · P-01 — Valores tomados del tema v2 REAL (`@heroui/theme@2.4.15`,
|
|
@@ -1268,6 +1637,44 @@ const VARIANT_CLASS$5 = {
|
|
|
1268
1637
|
* En reposo no cambia nada (el anillo de v3 ya vale 0 ahí), así que la referencia v2 se mantiene.
|
|
1269
1638
|
*/
|
|
1270
1639
|
const FIELD_FOCUS_CLASS$5 = "ring-0 ring-offset-0 outline-0";
|
|
1640
|
+
/*
|
|
1641
|
+
* TECH-2459 · hallazgo beweossmbs 23-sep — `labelPlacement` de v2, repuesta. Misma solución que
|
|
1642
|
+
* `TimeInput` en la .139: una tabla de clases por colocación. La barra de la agenda
|
|
1643
|
+
* (`agenda-toolbar.component.tsx`) pide `"outside-left"` y v1 no tenía la prop: la etiqueta caía
|
|
1644
|
+
* encima y la barra crecía una línea.
|
|
1645
|
+
*
|
|
1646
|
+
* `outside` es EXACTAMENTE lo que la fachada pintaba hasta ahora (la raíz `flex flex-col gap-1`,
|
|
1647
|
+
* sin clases de colocación en el grupo ni en la ayuda), así que quien no pasa la prop no cambia un
|
|
1648
|
+
* píxel. `outside-left` son los literales del `tv` de v2 (`@heroui/theme@2.4.15` →
|
|
1649
|
+
* `dist/components/date-input.js`, `labelPlacement["outside-left"]`, que es el `tv` del que tira el
|
|
1650
|
+
* `DatePicker` de v2):
|
|
1651
|
+
*
|
|
1652
|
+
* base → "flex-row items-center gap-x-2 flex-nowrap"
|
|
1653
|
+
* + `data-[has-helper=true]:pb-[calc(theme(fontSize.tiny)_+_8px)]`
|
|
1654
|
+
* inputWrapper → "relative flex-1"
|
|
1655
|
+
* helperWrapper→ "absolute top-[calc(100%_+_2px)] start-0"
|
|
1656
|
+
*
|
|
1657
|
+
* La ayuda y el error no pueden ser un tercer hijo de la fila (quedarían a la derecha del campo):
|
|
1658
|
+
* v2 los sacaba del flujo bajo el campo y reservaba su alto con el `pb` de la raíz. Aquí igual;
|
|
1659
|
+
* `theme(fontSize.tiny)` de v2 (12px) es `--text-xs` en Tailwind 4, y la raíz lleva `relative`
|
|
1660
|
+
* para que el `absolute` se ancle a ella.
|
|
1661
|
+
*/
|
|
1662
|
+
const ROOT_PLACEMENT_CLASS = {
|
|
1663
|
+
outside: "flex flex-col gap-1",
|
|
1664
|
+
"outside-left": "relative flex flex-row items-center gap-x-2 flex-nowrap",
|
|
1665
|
+
};
|
|
1666
|
+
const ROOT_HELPER_SPACE_CLASS = {
|
|
1667
|
+
outside: undefined,
|
|
1668
|
+
"outside-left": "pb-[calc(var(--text-xs)+8px)]",
|
|
1669
|
+
};
|
|
1670
|
+
const GROUP_PLACEMENT_CLASS = {
|
|
1671
|
+
outside: undefined,
|
|
1672
|
+
"outside-left": "relative flex-1",
|
|
1673
|
+
};
|
|
1674
|
+
const HELPER_PLACEMENT_CLASS = {
|
|
1675
|
+
outside: undefined,
|
|
1676
|
+
"outside-left": "absolute top-[calc(100%+2px)] start-0",
|
|
1677
|
+
};
|
|
1271
1678
|
// TECH-2459 · F5 · P-10 — El "círculo" del calendario. v2 (`@heroui/theme@2.4.15`,
|
|
1272
1679
|
// `calendar.ts`) solo tenía UN círculo y significaba "día seleccionado":
|
|
1273
1680
|
// cellButton: "w-8 h-8 … rounded-full" + color primary: "data-[selected=true]:bg-primary
|
|
@@ -1294,7 +1701,7 @@ const CALENDAR_CELL_CLASS$1 = "size-8 place-self-center rounded-full " +
|
|
|
1294
1701
|
* (grid) para que la API pública siga siendo un único componente con props planas, igual que en
|
|
1295
1702
|
* v2 (label, description, errorMessage, endContent, variant, size, radius).
|
|
1296
1703
|
*/
|
|
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 }) {
|
|
1704
|
+
function DatePicker({ label, description, errorMessage, id, size = "md", variant = "bordered", radius = "md", labelPlacement = "outside", isInvalid = false, isRequired = false, isDisabled = false, fullWidth, endContent, calendarBottomContent: calendarBottomContentProp, CalendarBottomContent, calendarTopContent: calendarTopContentProp, CalendarTopContent, calendarWidth, calendarProps, classNames, className, "aria-label": ariaLabel, ...props }) {
|
|
1298
1705
|
const generatedId = $8WNJc$react.useId();
|
|
1299
1706
|
const autoId = id || generatedId;
|
|
1300
1707
|
const calendarLabel = (typeof label === "string" ? label : undefined) ??
|
|
@@ -1302,17 +1709,24 @@ function DatePicker({ label, description, errorMessage, id, size = "md", variant
|
|
|
1302
1709
|
"Calendario";
|
|
1303
1710
|
const calendarBottomContent = calendarBottomContentProp ?? CalendarBottomContent;
|
|
1304
1711
|
const calendarTopContent = calendarTopContentProp ?? CalendarTopContent;
|
|
1712
|
+
const hasHelper = Boolean((description && !isInvalid) || (isInvalid && errorMessage));
|
|
1305
1713
|
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([
|
|
1714
|
+
return (jsxRuntime.jsxs(react.DatePicker, { ...props, id: autoId, "aria-label": ariaLabel, isInvalid: isInvalid, isRequired: isRequired, isDisabled: isDisabled, className: withConsumerClass([
|
|
1715
|
+
ROOT_PLACEMENT_CLASS[labelPlacement],
|
|
1716
|
+
fullWidth && "w-full",
|
|
1717
|
+
hasHelper && ROOT_HELPER_SPACE_CLASS[labelPlacement],
|
|
1718
|
+
classNames?.base,
|
|
1719
|
+
], className), children: [label && (jsxRuntime.jsx(react.Label, { className: withConsumerClass(["text-default-600 text-xs font-medium"], classNames?.label), children: label })), jsxRuntime.jsxs(react.DateField.Group, { fullWidth: fullWidth, className: withConsumerClass([
|
|
1307
1720
|
SIZE_CLASS$5[size],
|
|
1308
1721
|
RADIUS_CLASS$8[radius],
|
|
1309
1722
|
VARIANT_CLASS$5[variant],
|
|
1310
1723
|
FIELD_FOCUS_CLASS$5,
|
|
1311
|
-
|
|
1724
|
+
GROUP_PLACEMENT_CLASS[labelPlacement],
|
|
1725
|
+
], classNames?.inputWrapper), children: [jsxRuntime.jsx(react.DateField.Input, { className: withConsumerClass([SIZE_TEXT_CLASS$4[size]], classNames?.input), children: (segment) => (jsxRuntime.jsx(react.DateField.Segment, { segment: segment, className: classNames?.segment })) }), jsxRuntime.jsx(react.DateField.Suffix, { children: jsxRuntime.jsx(react.DatePicker.Trigger, { className: classNames?.selectorButton, children: endContent !== undefined ? (jsxRuntime.jsx(react.DatePicker.TriggerIndicator, { className: classNames?.selectorIcon, children: endContent })) : (jsxRuntime.jsx(react.DatePicker.TriggerIndicator, { className: classNames?.selectorIcon })) }) })] }), description && !isInvalid && (jsxRuntime.jsx(react.Description, { className: withConsumerClass([HELPER_PLACEMENT_CLASS[labelPlacement]], classNames?.description) || undefined, children: description })), jsxRuntime.jsxs(react.DatePicker.Popover, { className: classNames?.popoverContent, children: [calendarTopContent, jsxRuntime.jsxs(react.Calendar, { "aria-label": calendarLabel, minValue: props.minValue, maxValue: props.maxValue, ...restCalendarProps, className: withConsumerClass([
|
|
1312
1726
|
typeof calendarClassName === "string"
|
|
1313
1727
|
? calendarClassName
|
|
1314
1728
|
: 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 }))] }));
|
|
1729
|
+
], classNames?.calendar) || undefined, style: calendarWidthStyle(calendarWidth, typeof calendarStyle === "object" ? calendarStyle : undefined), children: [jsxRuntime.jsxs(react.Calendar.Header, { children: [jsxRuntime.jsxs(react.Calendar.YearPickerTrigger, { children: [jsxRuntime.jsx(react.Calendar.YearPickerTriggerHeading, {}), jsxRuntime.jsx(react.Calendar.YearPickerTriggerIndicator, {})] }), jsxRuntime.jsx(react.Calendar.NavButton, { slot: "previous" }), jsxRuntime.jsx(react.Calendar.NavButton, { slot: "next" })] }), jsxRuntime.jsxs(react.Calendar.Grid, { children: [jsxRuntime.jsx(react.Calendar.GridHeader, { children: (day) => jsxRuntime.jsx(react.Calendar.HeaderCell, { children: day }) }), jsxRuntime.jsx(react.Calendar.GridBody, { children: (date) => (jsxRuntime.jsx(react.Calendar.Cell, { date: date, className: CALENDAR_CELL_CLASS$1 })) })] })] }), calendarBottomContent] }), isInvalid && errorMessage && (jsxRuntime.jsx(react.FieldError, { className: withConsumerClass([HELPER_PLACEMENT_CLASS[labelPlacement]], classNames?.errorMessage) || undefined, children: errorMessage }))] }));
|
|
1316
1730
|
}
|
|
1317
1731
|
|
|
1318
1732
|
// Mismo mecanismo que DatePicker.tsx / Input.tsx / Select.tsx (findings/
|
|
@@ -1723,6 +2137,10 @@ const INVALID_TEXT_CLASS$2 = "text-danger";
|
|
|
1723
2137
|
* - Radius configurable (v3 no tiene la prop).
|
|
1724
2138
|
* - `startContent`/`endContent` → `InputGroup.Prefix`/`InputGroup.Suffix`.
|
|
1725
2139
|
* - `isClearable` → botón de limpiar propio (`CloseButton`); solo en modo controlado (`value`).
|
|
2140
|
+
* - `onClear` (API v2) → basta para mostrar la X; se llama al limpiar (X o Escape) y el foco
|
|
2141
|
+
* vuelve al campo, como en v2.
|
|
2142
|
+
* - `classNames`: los 10 slots de v2, cada uno en su nodo equivalente (tabla en `Input.types.ts`).
|
|
2143
|
+
* - `labelPlacement` (API v2): aceptado y sin efecto — el label siempre va fuera.
|
|
1726
2144
|
*
|
|
1727
2145
|
* `ref` apunta al `<input>` REAL (`HTMLInputElement`), no al `InputGroup` ni al `TextField` que lo
|
|
1728
2146
|
* envuelven: el consumidor que necesita el nodo —insertar texto en la posición del cursor con
|
|
@@ -1742,7 +2160,10 @@ const Input = $8WNJc$react.forwardRef(({ label, description, errorMessage, id,
|
|
|
1742
2160
|
// `lg` que había aquí venía del `argTypes.defaultValue.summary` de la story, que YA estaba mal
|
|
1743
2161
|
// en v2. Con "lg" todos los campos de las 4 apps que no pasan `size` salían 8px más altos, con
|
|
1744
2162
|
// 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
|
|
2163
|
+
size = "md", variant = "bordered", radius = "md", placeholder, startContent, endContent, isClearable: isClearableProp = false, onClear,
|
|
2164
|
+
// Sin efecto (ver `InputProps.labelPlacement`): se saca del spread para que no llegue
|
|
2165
|
+
// al `<input>` como atributo desconocido.
|
|
2166
|
+
labelPlacement: _labelPlacement, isInvalid = false, isRequired = false, isDisabled = false, isReadOnly = false,
|
|
1746
2167
|
// `fullWidth` por defecto a `true`: HeroUI v2 lo traía en sus `defaultVariants`
|
|
1747
2168
|
// (verificado en el paquete v2 compilado, `dist/components/input.js`:
|
|
1748
2169
|
// `defaultVariants: { variant:"flat", color:"default", size:"md", fullWidth:true, … }`, con
|
|
@@ -1750,7 +2171,21 @@ size = "md", variant = "bordered", radius = "md", placeholder, startContent, end
|
|
|
1750
2171
|
// `undefined` y el campo dejaba de llenar su contenedor: en una fila flex se encogía al ancho
|
|
1751
2172
|
// de su contenido (medido por S2: 208px dentro de una fila de 592, 336px de hueco). En una
|
|
1752
2173
|
// 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) => {
|
|
2174
|
+
fullWidth = true, value, defaultValue, onChange, onValueChange, className, classNames, required, name, type = "text", onKeyDown, ...rest }, ref) => {
|
|
2175
|
+
// El `<input>` real, también para la fachada: v2 devolvía el foco al campo al limpiar
|
|
2176
|
+
// (`domRef.current?.focus()` en `handleClear`). Se combina con el `ref` del consumidor.
|
|
2177
|
+
const inputRef = $8WNJc$react.useRef(null);
|
|
2178
|
+
const setInputRef = $8WNJc$react.useCallback((node) => {
|
|
2179
|
+
inputRef.current = node;
|
|
2180
|
+
if (typeof ref === "function") {
|
|
2181
|
+
ref(node);
|
|
2182
|
+
}
|
|
2183
|
+
else if (ref) {
|
|
2184
|
+
ref.current = node;
|
|
2185
|
+
}
|
|
2186
|
+
}, [ref]);
|
|
2187
|
+
// v2: `isClearable = !!onClear || originalProps.isClearable` (`use-input.ts`).
|
|
2188
|
+
const isClearable = isClearableProp || Boolean(onClear);
|
|
1754
2189
|
const generatedId = $8WNJc$react.useId();
|
|
1755
2190
|
const inputId = id || generatedId;
|
|
1756
2191
|
// ⚠️ `isRequired` (prop de HeroUI, asterisco visual) y `required` (atributo HTML nativo, solo
|
|
@@ -1758,7 +2193,24 @@ fullWidth = true, value, defaultValue, onChange, onValueChange, className, class
|
|
|
1758
2193
|
// Detectado por VRT (`FormExample`, que solo pasa `required`): tratarlos como lo mismo aquí
|
|
1759
2194
|
// introducía un asterisco que v2 no tenía. `isRequired` maneja el asterisco (vía TextField);
|
|
1760
2195
|
// `required` se reenvía tal cual al `<input>` nativo, sin efecto visual.
|
|
1761
|
-
|
|
2196
|
+
// ── El valor lo guarda el `TextField`, no el `<input>` ───────────────────────────────────
|
|
2197
|
+
//
|
|
2198
|
+
// En react-aria el dueño del valor es el `TextField`: `useTextField` lo guarda con
|
|
2199
|
+
// `useControlledState(value, defaultValue ?? "")` y se lo inyecta al `<input>` como `value`
|
|
2200
|
+
// por contexto (`react-aria-components/dist/private/TextField.mjs`). Antes la fachada pasaba
|
|
2201
|
+
// `value`/`defaultValue` al `InputGroup.Input` y ninguno al `TextField`, así que en modo NO
|
|
2202
|
+
// controlado el `TextField` arrancaba en "" y el `<input>` recibía a la vez `value=""` (del
|
|
2203
|
+
// contexto) y `defaultValue`: medido en jsdom, `<Input defaultValue="hola" />` salía VACÍO
|
|
2204
|
+
// ya en el primer render, con el aviso de React «both value and defaultValue». Mismo arreglo
|
|
2205
|
+
// que en `Textarea.tsx`.
|
|
2206
|
+
//
|
|
2207
|
+
// Aquí además la fachada lleva el valor no controlado en su propio estado (en vez de
|
|
2208
|
+
// dejárselo al `TextField`), porque limpiar tiene que poder vaciarlo: `isClearable`/`onClear`
|
|
2209
|
+
// en modo no controlado no tenían manera de escribir en el estado interno del `TextField`.
|
|
2210
|
+
const isControlled = value !== undefined;
|
|
2211
|
+
const [uncontrolledValue, setUncontrolledValue] = $8WNJc$react.useState(defaultValue ?? "");
|
|
2212
|
+
const currentValue = isControlled ? value : uncontrolledValue;
|
|
2213
|
+
const showClear = isClearable && !isDisabled && !isReadOnly && Boolean(currentValue);
|
|
1762
2214
|
// Solo flota cuando hay label y NO hay placeholder: con placeholder, v2 ya mostraba el label
|
|
1763
2215
|
// arriba desde el primer render (ver el bloque de arriba).
|
|
1764
2216
|
const labelFlotante = Boolean(label) && !placeholder;
|
|
@@ -1766,7 +2218,12 @@ fullWidth = true, value, defaultValue, onChange, onValueChange, className, class
|
|
|
1766
2218
|
onChange?.(event);
|
|
1767
2219
|
onValueChange?.(event.target.value);
|
|
1768
2220
|
};
|
|
1769
|
-
|
|
2221
|
+
// Orden de v2 (`handleClear` de `use-input.ts`): primero el valor a "" —que en v2 disparaba
|
|
2222
|
+
// `onValueChange`—, luego `onClear`, y el foco vuelve al campo.
|
|
2223
|
+
const clearValue = () => {
|
|
2224
|
+
if (!isControlled) {
|
|
2225
|
+
setUncontrolledValue("");
|
|
2226
|
+
}
|
|
1770
2227
|
onValueChange?.("");
|
|
1771
2228
|
if (onChange) {
|
|
1772
2229
|
const fakeEvent = {
|
|
@@ -1775,15 +2232,38 @@ fullWidth = true, value, defaultValue, onChange, onValueChange, className, class
|
|
|
1775
2232
|
};
|
|
1776
2233
|
onChange(fakeEvent);
|
|
1777
2234
|
}
|
|
2235
|
+
onClear?.();
|
|
2236
|
+
};
|
|
2237
|
+
const handleClear = () => {
|
|
2238
|
+
clearValue();
|
|
2239
|
+
inputRef.current?.focus();
|
|
2240
|
+
};
|
|
2241
|
+
// Escape limpia, igual que v2 (`handleKeyDown` de `use-input.ts`: con valor, limpiable y
|
|
2242
|
+
// no de solo lectura). El `onKeyDown` del consumidor se llama primero.
|
|
2243
|
+
const handleKeyDown = (event) => {
|
|
2244
|
+
onKeyDown?.(event);
|
|
2245
|
+
if (event.key === "Escape" && showClear) {
|
|
2246
|
+
clearValue();
|
|
2247
|
+
}
|
|
1778
2248
|
};
|
|
1779
|
-
return (jsxRuntime.jsxs(react.TextField, { id: inputId,
|
|
2249
|
+
return (jsxRuntime.jsxs(react.TextField, { id: inputId, value: currentValue,
|
|
2250
|
+
// Solo el modo no controlado escribe aquí; en controlado manda el `value` del padre
|
|
2251
|
+
// (que se entera por `onChange`/`onValueChange` del `<input>`).
|
|
2252
|
+
onChange: isControlled ? undefined : setUncontrolledValue, name: name, isInvalid: isInvalid, isRequired: isRequired, isDisabled: isDisabled, isReadOnly: isReadOnly, className: react.cn(FIELD_STACK_CLASS$3[size], fullWidth ? "w-full" : "",
|
|
2253
|
+
// `mainWrapper` de v2 no tiene nodo propio aquí (ver tabla en `Input.types.ts`):
|
|
2254
|
+
// se fusiona en la raíz antes que `base`, que es el slot más externo.
|
|
2255
|
+
classNames?.mainWrapper,
|
|
2256
|
+
// `base` de v2 = la raíz. v2 los juntaba así: `clsx(classNames.base, className)`.
|
|
2257
|
+
classNames?.base,
|
|
1780
2258
|
// El `className` del consumidor, en la raíz y en último lugar: el slot `base` de
|
|
1781
2259
|
// v2 y la posición que le deja ganar en tailwind-merge (ver bloque de arriba).
|
|
1782
2260
|
className), children: [label && (jsxRuntime.jsx(react.Label, { className: react.cn(LABEL_CLASS$4, LABEL_LEFT_CLASS[variant], variant === "underlined" ? "top-0" : LABEL_OFFSET_CLASS$1[size], isInvalid ? INVALID_LABEL_CLASS$2 : "", labelFlotante ? LABEL_INSIDE_CLASS[size] : "", labelFlotante ? LABEL_FLOAT_CLASS : "", classNames?.label), children: label })), jsxRuntime.jsxs(react.InputGroup, { isInvalid: isInvalid, isDisabled: isDisabled, isReadOnly: isReadOnly, className: react.cn(SIZE_CLASS$3[size], radius === "full" ? PADDING_FULL_CLASS$1[size] : PADDING_CLASS$2[size], variant === "underlined" ? UNDERLINED_INNER_CLASS$1[size] : "", RADIUS_CLASS$6[radius], VARIANT_CLASS$3[variant], FIELD_FOCUS_CLASS$3, FIELD_SHADOW_CLASS$2,
|
|
1783
2261
|
// Incondicional, como el `inputWrapper` de v2: la caja del campo llena
|
|
1784
2262
|
// siempre su raíz, y es la raíz la que responde a `fullWidth` y al ancho que
|
|
1785
2263
|
// pase el consumidor.
|
|
1786
|
-
"w-full",
|
|
2264
|
+
"w-full",
|
|
2265
|
+
// v3 fusiona `innerWrapper` e `inputWrapper` de v2 en este único elemento.
|
|
2266
|
+
classNames?.innerWrapper, classNames?.inputWrapper), children: [startContent && (jsxRuntime.jsx(react.InputGroup.Prefix, { className: "border-none bg-transparent px-0", children: startContent })), jsxRuntime.jsx(react.InputGroup.Input, { ...rest, ref: setInputRef, onKeyDown: handleKeyDown, required: required, type: type, placeholder: labelFlotante ? " " : placeholder, onChange: handleChange, className: react.cn("bg-transparent p-0", SIZE_TEXT_CLASS$2[size], isInvalid ? INVALID_TEXT_CLASS$2 : "", startContent ? "ps-1.5" : "", endContent || showClear ? "pe-1.5" : "", classNames?.input) }), showClear && (jsxRuntime.jsx(react.CloseButton, { "aria-label": "Limpiar", className: react.cn("h-4 w-4 min-h-0 p-0.5 mr-1", classNames?.clearButton), onPress: handleClear })), endContent && (jsxRuntime.jsx(react.InputGroup.Suffix, { className: "border-none bg-transparent px-0", children: endContent }))] }), description && !isInvalid && (jsxRuntime.jsx(react.Description, { className: react.cn(HELPER_CLASS$2, "text-left", classNames?.helperWrapper, classNames?.description), children: description })), isInvalid && errorMessage && (jsxRuntime.jsx(react.FieldError, { className: react.cn(HELPER_CLASS$2, "text-left", classNames?.helperWrapper, classNames?.errorMessage), children: errorMessage }))] }));
|
|
1787
2267
|
});
|
|
1788
2268
|
Input.displayName = "Input";
|
|
1789
2269
|
|
|
@@ -1890,12 +2370,36 @@ const RADIUS_CLASS$5 = {
|
|
|
1890
2370
|
// v3 no tiene prop `shadow`: su Card siempre usa `shadow-surface`. Los valores de v2 se
|
|
1891
2371
|
// reproducen con los tokens propios `--aurora-shadow-*` (emitidos en F1, mismo valor exacto que
|
|
1892
2372
|
// generaba el plugin `heroui()` de v2 — ver findings/tabla-mapeo-tokens.md §2).
|
|
2373
|
+
//
|
|
2374
|
+
// ⚠️ El prefijo `shadow:` dentro del corchete NO es decorativo. Sin él, tailwind-merge 3 clasifica
|
|
2375
|
+
// `shadow-[var(--x)]` como COLOR de sombra (grupo `shadow-color`), no como sombra: medido con el
|
|
2376
|
+
// `cn` real, `cn("shadow-[var(--aurora-shadow-small)]", "shadow-2xl")` conserva LAS DOS, y en el
|
|
2377
|
+
// CSS de Tailwind 4 la arbitraria se emite después de `shadow-2xl` (orden de candidato), así que
|
|
2378
|
+
// ganaba la nuestra — el `shadow-2xl` que pide `whatsapp-setup-modal` no se veía. Con la etiqueta
|
|
2379
|
+
// `shadow:` tailwind-merge la reconoce como sombra y la retira cuando la app pasa la suya; el CSS
|
|
2380
|
+
// que emite Tailwind 4 es idéntico (`--tw-shadow: var(--aurora-shadow-small)`, comprobado con
|
|
2381
|
+
// `@tailwindcss/node`).
|
|
1893
2382
|
const SHADOW_CLASS = {
|
|
1894
2383
|
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
|
-
};
|
|
2384
|
+
sm: "shadow-[shadow:var(--aurora-shadow-small)]",
|
|
2385
|
+
md: "shadow-[shadow:var(--aurora-shadow-medium)]",
|
|
2386
|
+
lg: "shadow-[shadow:var(--aurora-shadow-large)]",
|
|
2387
|
+
};
|
|
2388
|
+
// Literales del `tv` de v2 (`@heroui/theme@2.4.15`, `dist/components/card.js`), con sus tokens
|
|
2389
|
+
// traducidos a los que existen en Tailwind 4 + Aurora:
|
|
2390
|
+
// isPressable → `cursor-pointer` + compound `data-[pressed=true]:scale-[0.97]
|
|
2391
|
+
// tap-highlight-transparent` (el hundido lo hace aquí `:active`, que es lo que
|
|
2392
|
+
// marcaba `data-pressed` en un botón nativo)
|
|
2393
|
+
// disableAnimation=false → `transition-transform-background motion-reduce:transition-none`
|
|
2394
|
+
// foco → `dataFocusVisibleClasses` (`outline-2 outline-focus outline-offset-2`)
|
|
2395
|
+
// isHoverable → `data-[hover=true]:bg-content2` (aquí `hover:`, react-aria ya no escribe
|
|
2396
|
+
// `data-hover` — trampa 3 de la skill `auraui-components`)
|
|
2397
|
+
// isDisabled → `opacity-disabled cursor-not-allowed` (`opacity-disabled` era 0.5 en v2)
|
|
2398
|
+
// fullWidth → `w-full`
|
|
2399
|
+
const PRESSABLE_CLASS = "isolate cursor-pointer text-foreground outline-none focus-visible:z-10 focus-visible:outline-2 focus-visible:outline-focus focus-visible:outline-offset-2 [-webkit-tap-highlight-color:transparent]";
|
|
2400
|
+
const PRESS_ANIMATION_CLASS = "transition-[transform,background-color] motion-reduce:transition-none active:scale-[0.97]";
|
|
2401
|
+
const HOVERABLE_CLASS = "hover:bg-content2";
|
|
2402
|
+
const DISABLED_CLASS = "opacity-50 cursor-not-allowed";
|
|
1899
2403
|
/**
|
|
1900
2404
|
* Card genérica siguiendo las reglas de diseño BeweOS basada en HeroUI v3.
|
|
1901
2405
|
*
|
|
@@ -1909,18 +2413,50 @@ const SHADOW_CLASS = {
|
|
|
1909
2413
|
* - Shadow: none, sm, md, lg (configurable, reimplementado vía className — v3 no tiene la prop)
|
|
1910
2414
|
* - Radius: sm (por defecto, configurable, reimplementado vía className — v3 no tiene la prop)
|
|
1911
2415
|
* - Padding configurable (sm, md, lg)
|
|
2416
|
+
* - `className` de la app AL FINAL y fusionado con `cn` (tailwind-merge), como el `slots.base({
|
|
2417
|
+
* class })` de v2: un `p-0`/`rounded-xl`/`shadow-2xl` de la app retira el default de la fachada
|
|
2418
|
+
* en vez de competir con él por orden de CSS.
|
|
2419
|
+
* - `ref` y TODAS las props DOM (`style`, `onMouse*`, `aria-*`, `data-*`…) llegan al nodo raíz,
|
|
2420
|
+
* como en v2 (`HTMLHeroUIProps<"div">`).
|
|
2421
|
+
* - API v2 de card pulsable: `isPressable`/`onPress`/`disableAnimation`/`disableRipple`, más
|
|
2422
|
+
* `isHoverable`/`isDisabled`/`fullWidth`.
|
|
2423
|
+
*
|
|
2424
|
+
* `ref` se reenvía con `forwardRef` (patrón de las fachadas de campo del repo, ver
|
|
2425
|
+
* `Input.tsx`) y apunta al nodo raíz: el `<div>` o, con `isPressable`, el `<button>`.
|
|
1912
2426
|
*/
|
|
1913
|
-
const Card = ({ children, className
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
2427
|
+
const Card = $8WNJc$react.forwardRef(({ children, className, shadow = "sm", radius = "sm", padding = "md", isPressable = false, onPress, onClick, disableAnimation = false, disableRipple = false, isHoverable = false, isDisabled = false, fullWidth = false, render, ...props }, ref) => {
|
|
2428
|
+
// Como en v2 (`@heroui/card@2.2.21`, `useCard`): `disableAnimation` apaga también la onda.
|
|
2429
|
+
const sinRipple = disableRipple || disableAnimation;
|
|
2430
|
+
const { ondas, lanzar, quitar } = useRipple();
|
|
2431
|
+
const handleClick = $8WNJc$react.useCallback((event) => {
|
|
2432
|
+
onClick?.(event);
|
|
2433
|
+
if (!isPressable || isDisabled) {
|
|
2434
|
+
return;
|
|
2435
|
+
}
|
|
2436
|
+
const pressEvent = pressEventFromClick(event);
|
|
2437
|
+
onPress?.(pressEvent);
|
|
2438
|
+
if (!sinRipple) {
|
|
2439
|
+
lanzar(pressEvent);
|
|
2440
|
+
}
|
|
2441
|
+
}, [onClick, isPressable, isDisabled, onPress, sinRipple, lanzar]);
|
|
2442
|
+
const combinedClassName = react.cn(paddingClasses[padding], RADIUS_CLASS$5[radius], SHADOW_CLASS[shadow], fullWidth ? "w-full" : "", isPressable ? PRESSABLE_CLASS : "", isPressable && !disableAnimation ? PRESS_ANIMATION_CLASS : "", isHoverable ? HOVERABLE_CLASS : "", isDisabled ? DISABLED_CLASS : "",
|
|
2443
|
+
// El `className` de la app, último: gana por fusión (ver JSDoc).
|
|
2444
|
+
className);
|
|
2445
|
+
// v2 cambiaba el ELEMENTO a `<button>` con `isPressable` (`Component = as || (isPressable
|
|
2446
|
+
// ? "button" : "div")`). v3 lo permite con `render` (DOMRenderProps: basta con conectar
|
|
2447
|
+
// el `ref`, que ya viene en `domProps`), así que se conserva el `CardContext` del
|
|
2448
|
+
// compound y los `Card.Header`/`CardBody` de dentro siguen funcionando.
|
|
2449
|
+
const pressableRender = isPressable
|
|
2450
|
+
? (domProps) => (jsxRuntime.jsx("button", { ...domProps, type: "button",
|
|
2451
|
+
// v2 (`useCard`): `{ ...buttonProps, ...focusProps, role: "button" }` y
|
|
2452
|
+
// `tabIndex: isPressable ? 0 : -1`. En un `<button>` nativo el rol ya es
|
|
2453
|
+
// `button` implícito (biome marca el explícito como redundante); el
|
|
2454
|
+
// `tabIndex` se escribe igual que v2.
|
|
2455
|
+
tabIndex: isDisabled ? -1 : 0, disabled: isDisabled }))
|
|
2456
|
+
: render;
|
|
2457
|
+
return (jsxRuntime.jsxs(react.Card, { ...props, ref: ref, className: combinedClassName, "data-pressable": isPressable || undefined, "data-disabled": isDisabled || undefined, onClick: isPressable || onClick ? handleClick : undefined, render: pressableRender, children: [children, isPressable && ondas.length > 0 && (jsxRuntime.jsx(CapaRipple, { ondas: ondas, onFin: quitar }))] }));
|
|
2458
|
+
});
|
|
2459
|
+
Card.displayName = "Card";
|
|
1924
2460
|
|
|
1925
2461
|
const H2 = ({ children, className, ...props }) => {
|
|
1926
2462
|
return (jsxRuntime.jsx("h2", { className: react.cn("text-lg font-semibold", className), ...props, children: children }));
|
|
@@ -2158,10 +2694,221 @@ function withCollectionItemIds(children) {
|
|
|
2158
2694
|
return Object.keys(patch).length > 0 ? $8WNJc$react.cloneElement(element, patch) : element;
|
|
2159
2695
|
}
|
|
2160
2696
|
|
|
2697
|
+
/**
|
|
2698
|
+
* Repone en los items y secciones de una colección de v3 la API de CONTENIDO de v2 que las apps
|
|
2699
|
+
* siguen escribiendo en `SelectItem`/`AutocompleteItem`/`SelectSection` (TECH-2459, hallazgo
|
|
2700
|
+
* beweossmbs 23-sep).
|
|
2701
|
+
*
|
|
2702
|
+
* QUÉ HACÍA v2 (`@heroui/listbox@2.3.31`, `dist/chunk-2TBDFBA3.mjs`, leído del tarball):
|
|
2703
|
+
*
|
|
2704
|
+
* <li …getItemProps()>
|
|
2705
|
+
* {startContent && <span data-slot="startContent">…}
|
|
2706
|
+
* {description
|
|
2707
|
+
* ? <div wrapper><span title>{rendered}</span><span description>{description}</span></div>
|
|
2708
|
+
* : <span title>{rendered}</span>}
|
|
2709
|
+
* {selectedIcon}
|
|
2710
|
+
* {endContent && <span data-slot="endContent">…}
|
|
2711
|
+
* </li>
|
|
2712
|
+
*
|
|
2713
|
+
* con los slots del `tv` (`@heroui/theme@2.4.15`, `dist/components/listbox.js:100-139`):
|
|
2714
|
+
* `wrapper: "w-full flex flex-col items-start justify-center"`, `title: "flex-1 text-small
|
|
2715
|
+
* font-normal"`, `description: "w-full text-tiny text-foreground-500 …"`. La sección
|
|
2716
|
+
* (`chunk-Q2EVG7SO.mjs`) pintaba la cabecera `heading: "pl-1 text-tiny text-foreground-500"` y,
|
|
2717
|
+
* con `showDivider` (default `false`), un `Divider` con `mt-2` al final.
|
|
2718
|
+
*
|
|
2719
|
+
* QUÉ HACE v3. `ListBox.Item` no tiene NINGUNA de esas props (la guía oficial: «Place icons
|
|
2720
|
+
* manually in item content», «Use `Description` component»; y `title` de la sección → `Header`).
|
|
2721
|
+
* Pasadas tal cual se descartan en silencio: el `filterDOMProps` de react-aria no las deja llegar
|
|
2722
|
+
* al DOM y el item pinta solo su texto.
|
|
2723
|
+
*
|
|
2724
|
+
* POR QUÉ AQUÍ Y NO EN EL SHIM. `SelectItem`/`AutocompleteItem`/`SelectSection` tienen que seguir
|
|
2725
|
+
* siendo el MISMO objeto que `ListBoxItem`/`ListBoxSection`: el normalizador de ids
|
|
2726
|
+
* (`utils/collection-items.ts`) y el indicador (`with-item-indicator.tsx`) reconocen los items por
|
|
2727
|
+
* identidad de `element.type`, y un envoltorio los volvería invisibles (los items dentro de una
|
|
2728
|
+
* sección perderían su `id` derivado de la `key`, que es el defecto nº 1 de la migración). Así que
|
|
2729
|
+
* el shim solo TIPA las props v2 y esta función, llamada desde la fachada que tiene los items en
|
|
2730
|
+
* la mano, las traduce a la anatomía de v3.
|
|
2731
|
+
*
|
|
2732
|
+
* NO-OP cuando no hay nada que traducir: un item sin `description`/`startContent`/`endContent`/
|
|
2733
|
+
* `classNames` se devuelve tal cual (mismo objeto), así que los 150+ items planos de las apps no
|
|
2734
|
+
* cambian ni un nodo.
|
|
2735
|
+
*/
|
|
2736
|
+
/** Marca de lo que v2 NO pintaba en el disparador: allí solo iba el `textValue` del item. */
|
|
2737
|
+
const ITEM_EXTRA_ATTR = "data-aura-item-extra";
|
|
2738
|
+
/**
|
|
2739
|
+
* Clase para `Select.Value`: esconde en el disparador lo que v2 solo pintaba en la lista.
|
|
2740
|
+
*
|
|
2741
|
+
* `SelectValue` de react-aria repinta los CHILDREN del item elegido dentro del disparador
|
|
2742
|
+
* (`react-aria-components/dist/private/Select.mjs:226-262`). v2 pintaba ahí solo el `textValue`
|
|
2743
|
+
* (`@heroui/select@2.4.33`, `dist/chunk-BJWIL32D.mjs`: `state.selectedItems.map(i =>
|
|
2744
|
+
* i.textValue).join(", ")`), así que el icono lateral no debe colarse junto al valor. La
|
|
2745
|
+
* descripción se esconde SOLA: `Description` de v3 devuelve `null` sin el slot de texto
|
|
2746
|
+
* `description`, y `SelectValue` resetea ese contexto (`TextContext.Provider value={undefined}`,
|
|
2747
|
+
* misma línea) — el mismo mecanismo que usa v3 para esconder su indicador ahí (`select.css:121`).
|
|
2748
|
+
*/
|
|
2749
|
+
const HIDE_ITEM_EXTRAS_CLASS = `[&_[${ITEM_EXTRA_ATTR}]]:hidden`;
|
|
2750
|
+
// Literales de v2 portados a la pieza de v3, sin clases v2 muertas (`text-small`/`text-tiny` no
|
|
2751
|
+
// existen sin el plugin `heroui()` y `cn` las borraría). El tamaño del título NO se fija: el
|
|
2752
|
+
// desplegable de v3 ya es `text-sm` (= el `text-small` de v2) y dentro del disparador tiene que
|
|
2753
|
+
// heredar el tamaño del campo (`lg` = 16px).
|
|
2754
|
+
const ITEM_WRAPPER_CLASS = "flex w-full min-w-0 flex-col items-start justify-center";
|
|
2755
|
+
const ITEM_TITLE_CLASS$1 = "min-w-0 flex-1 font-normal";
|
|
2756
|
+
// `Description` de v3 ya trae `text-xs text-muted` (`description.css`), y `text-muted` es el mismo
|
|
2757
|
+
// zinc-500/400 que el `text-foreground-500` de v2 (medido en `Dropdown.tsx`, bloque de la cabecera).
|
|
2758
|
+
const ITEM_DESCRIPTION_CLASS = "w-full";
|
|
2759
|
+
const ITEM_SIDE_CLASS = "flex shrink-0 items-center";
|
|
2760
|
+
// Cabecera de sección: mismo único ajuste que la fachada `Dropdown` (ver `SECTION_HEADING_CLASS`
|
|
2761
|
+
// en `Dropdown.tsx`: tamaño y color de `.header` ya coinciden con v2; el peso no).
|
|
2762
|
+
const SECTION_HEADING_CLASS$1 = "font-normal";
|
|
2763
|
+
// `mt-2` del slot `divider` de v2; el ancho y el color los pone el `Separator` de v3.
|
|
2764
|
+
const SECTION_DIVIDER_CLASS = "mt-2";
|
|
2765
|
+
/** `isEmpty` de v2 (`@heroui/shared-utils`): null/undefined/""/false no cuentan. */
|
|
2766
|
+
function hasValue$2(node) {
|
|
2767
|
+
return node !== null && node !== undefined && node !== "" && node !== false;
|
|
2768
|
+
}
|
|
2769
|
+
function translateItem(element) {
|
|
2770
|
+
const { children, className, description, startContent, endContent, classNames, } = element.props;
|
|
2771
|
+
const hasDescription = hasValue$2(description);
|
|
2772
|
+
const hasStart = hasValue$2(startContent);
|
|
2773
|
+
const hasEnd = hasValue$2(endContent);
|
|
2774
|
+
if (!(hasDescription || hasStart || hasEnd || classNames)) {
|
|
2775
|
+
return element;
|
|
2776
|
+
}
|
|
2777
|
+
// Solo `classNames.base` (el caso de `timezone-search-select`): nada que recomponer, basta
|
|
2778
|
+
// fusionar la clase en la raíz. Y children como función (render props de react-aria): no se
|
|
2779
|
+
// puede componer alrededor sin romper la firma, así que se aplican solo las clases de la raíz.
|
|
2780
|
+
const onlyBase = !(hasDescription ||
|
|
2781
|
+
hasStart ||
|
|
2782
|
+
hasEnd ||
|
|
2783
|
+
classNames?.title ||
|
|
2784
|
+
classNames?.wrapper ||
|
|
2785
|
+
classNames?.description);
|
|
2786
|
+
if (onlyBase || typeof children === "function") {
|
|
2787
|
+
return $8WNJc$react.cloneElement(element, {
|
|
2788
|
+
className: react.cn(classNames?.base, className) || undefined,
|
|
2789
|
+
description: undefined,
|
|
2790
|
+
startContent: undefined,
|
|
2791
|
+
endContent: undefined,
|
|
2792
|
+
classNames: undefined,
|
|
2793
|
+
});
|
|
2794
|
+
}
|
|
2795
|
+
const title = (jsxRuntime.jsx("span", { className: react.cn(ITEM_TITLE_CLASS$1, classNames?.title), children: children }));
|
|
2796
|
+
const next = (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [hasStart && (jsxRuntime.jsx("span", { "data-slot": "start-content", [ITEM_EXTRA_ATTR]: "", className: ITEM_SIDE_CLASS, children: startContent })), hasDescription ? (jsxRuntime.jsxs("div", { className: react.cn(ITEM_WRAPPER_CLASS, classNames?.wrapper), children: [title, jsxRuntime.jsx(react.Description, { className: react.cn(ITEM_DESCRIPTION_CLASS, classNames?.description), children: description })] })) : (title), hasEnd && (jsxRuntime.jsx("span", { "data-slot": "end-content", [ITEM_EXTRA_ATTR]: "", className: ITEM_SIDE_CLASS, children: endContent }))] }));
|
|
2797
|
+
return $8WNJc$react.cloneElement(element, {
|
|
2798
|
+
// v2: `cn(classNames?.base, className)` (`use-listbox-item.mjs`), así que `className` gana.
|
|
2799
|
+
className: react.cn(classNames?.base, className) || undefined,
|
|
2800
|
+
description: undefined,
|
|
2801
|
+
startContent: undefined,
|
|
2802
|
+
endContent: undefined,
|
|
2803
|
+
classNames: undefined,
|
|
2804
|
+
children: next,
|
|
2805
|
+
});
|
|
2806
|
+
}
|
|
2807
|
+
function translateSection(element) {
|
|
2808
|
+
const { children, className, title, showDivider, classNames } = element.props;
|
|
2809
|
+
const inner = withV2ItemContent(children);
|
|
2810
|
+
const hasTitle = hasValue$2(title);
|
|
2811
|
+
if (!(hasTitle || showDivider || classNames) && inner === children) {
|
|
2812
|
+
return element;
|
|
2813
|
+
}
|
|
2814
|
+
// ⚠️ Los hijos van como ARGUMENTOS SUELTOS de `cloneElement`, no envueltos en un Fragment:
|
|
2815
|
+
// `withItemIndicator` (que corre después) y `withCollectionItemIds` bajan a los items de una
|
|
2816
|
+
// sección leyendo `props.children` como array o elemento, y un Fragment los escondería — los
|
|
2817
|
+
// items de la sección se quedarían sin checkmark.
|
|
2818
|
+
const parts = [];
|
|
2819
|
+
if (hasTitle) {
|
|
2820
|
+
parts.push(jsxRuntime.jsx(react.Header, { className: react.cn(SECTION_HEADING_CLASS$1, classNames?.heading), children: title }, "aura-section-header"));
|
|
2821
|
+
}
|
|
2822
|
+
parts.push(...(Array.isArray(inner) ? inner : [inner]));
|
|
2823
|
+
if (showDivider) {
|
|
2824
|
+
parts.push(jsxRuntime.jsx(react.Separator, { className: react.cn(SECTION_DIVIDER_CLASS, classNames?.divider) }, "aura-section-divider"));
|
|
2825
|
+
}
|
|
2826
|
+
return $8WNJc$react.cloneElement(element, {
|
|
2827
|
+
className: react.cn(classNames?.base, className) || undefined,
|
|
2828
|
+
// `title` en v3 es el atributo global de HTML: sin retirarlo sale como *tooltip*
|
|
2829
|
+
// nativo sobre toda la sección (el mismo motivo por el que v2 lo quitaba del spread,
|
|
2830
|
+
// `chunk-Q2EVG7SO.mjs`: «removed title from props to avoid browsers showing a tooltip»).
|
|
2831
|
+
title: undefined,
|
|
2832
|
+
showDivider: undefined,
|
|
2833
|
+
classNames: undefined,
|
|
2834
|
+
}, ...parts);
|
|
2835
|
+
}
|
|
2836
|
+
/**
|
|
2837
|
+
* Traduce la API de contenido v2 de cada item/sección de la colección. Va DESPUÉS de
|
|
2838
|
+
* `withCollectionItemIds` (que deriva el `textValue` del texto del item: si la descripción ya
|
|
2839
|
+
* estuviera dentro, la sumaría al texto de búsqueda y del disparador) y ANTES de
|
|
2840
|
+
* `withItemIndicator`.
|
|
2841
|
+
*
|
|
2842
|
+
* Acepta también children como FUNCIÓN (colección dinámica con `items`, la forma de 11 de los 11
|
|
2843
|
+
* `AuraAutocomplete` de beweossmbs): se envuelve la función para traducir lo que devuelva.
|
|
2844
|
+
*/
|
|
2845
|
+
function withV2ItemContent(children) {
|
|
2846
|
+
if (typeof children === "function") {
|
|
2847
|
+
const render = children;
|
|
2848
|
+
return ((item) => withV2ItemContent(render(item)));
|
|
2849
|
+
}
|
|
2850
|
+
if (Array.isArray(children)) {
|
|
2851
|
+
return children.map((child) => withV2ItemContent(child));
|
|
2852
|
+
}
|
|
2853
|
+
if (!$8WNJc$react.isValidElement(children)) {
|
|
2854
|
+
return children;
|
|
2855
|
+
}
|
|
2856
|
+
if (children.type === react.ListBoxSection) {
|
|
2857
|
+
return translateSection(children);
|
|
2858
|
+
}
|
|
2859
|
+
if (children.type === react.ListBoxItem) {
|
|
2860
|
+
return translateItem(children);
|
|
2861
|
+
}
|
|
2862
|
+
return children;
|
|
2863
|
+
}
|
|
2864
|
+
|
|
2161
2865
|
/** Traducciones por defecto en español (mismo literal que la ✕ de la fachada `Input`). */
|
|
2162
2866
|
const DEFAULT_TRANSLATIONS$f = {
|
|
2163
2867
|
clearButtonAriaLabel: "Limpiar",
|
|
2868
|
+
emptyContent: "No se encontraron resultados",
|
|
2164
2869
|
};
|
|
2870
|
+
/*
|
|
2871
|
+
* La lista vacía (TECH-2459, hallazgo beweossmbs 23-sep: 6 `AuraAutocomplete` pasan
|
|
2872
|
+
* `listboxProps.emptyContent` y ninguno se veía).
|
|
2873
|
+
*
|
|
2874
|
+
* v2 (`@heroui/autocomplete@2.3.34`, `dist/chunk-J7SBPTP2.mjs`, leído del tarball):
|
|
2875
|
+
*
|
|
2876
|
+
* allowsEmptyCollection = true, (l.36, default)
|
|
2877
|
+
* listboxProps: mergeProps({
|
|
2878
|
+
* hideEmptyContent: allowsCustomValue && !listboxProps?.emptyContent, (l.160)
|
|
2879
|
+
* emptyContent: "No results found.", (l.161)
|
|
2880
|
+
* }, listboxProps)
|
|
2881
|
+
*
|
|
2882
|
+
* O sea: abría la lista aunque el filtro la dejara vacía, y pintaba un texto — salvo con
|
|
2883
|
+
* `allowsCustomValue` sin texto propio (campo libre: "sin resultados" no es un error ahí). v3 invierte
|
|
2884
|
+
* el default (`allowsEmptyCollection` a `false`: la lista se CIERRA al quedarse vacía) y su `ListBox`
|
|
2885
|
+
* no pinta nada sin `renderEmptyState`. Se reponen las dos cosas con la misma regla de v2; un
|
|
2886
|
+
* `allowsEmptyCollection` explícito del consumidor sigue mandando. El texto por defecto va
|
|
2887
|
+
* traducido (patrón i18n del repo), no el inglés de v2.
|
|
2888
|
+
*
|
|
2889
|
+
* Medidas: las del estado vacío de `Select` (hueco del alto de una opción de v3, gris
|
|
2890
|
+
* `text-foreground-400` del slot `emptyContent` de v2), con el `px-2` del item de v3 porque este
|
|
2891
|
+
* popover no pisa el padding de los items como sí hace el del Select.
|
|
2892
|
+
*/
|
|
2893
|
+
const EMPTY_CONTENT_CLASS$1 = "flex min-h-9 w-full items-center px-2 py-1.5 text-start text-foreground-400";
|
|
2894
|
+
/*
|
|
2895
|
+
* El mensaje de error (v2 `errorMessage`, 3 usos en beweossmbs que hoy no se veían). v2 lo pintaba
|
|
2896
|
+
* bajo el campo en el `helperWrapper` del `Input` (`p-1` + `text-tiny text-danger`), solo con el
|
|
2897
|
+
* campo inválido. `FieldError` de v3 ya trae `px-1 text-xs text-danger` (`field-error.css`) y se
|
|
2898
|
+
* muestra solo cuando react-aria da el campo por inválido; el `text-left` es por si el campo vive
|
|
2899
|
+
* en un contenedor centrado. El hueco lo da el `gap-2` del stack, en vez de los 4px del `p-1`: la
|
|
2900
|
+
* diferencia es de 4px y se deja en el ritmo de v3 (§9.1).
|
|
2901
|
+
*/
|
|
2902
|
+
const ERROR_MESSAGE_CLASS = "text-left";
|
|
2903
|
+
/*
|
|
2904
|
+
* `isLoading` (v2): el `selectorButton` pasaba a `isLoading` y su spinner ocupaba el sitio del
|
|
2905
|
+
* chevron (`chunk-J7SBPTP2.mjs:168`). ⚠️ En la fachada de v2 eso no se veía con la lupa puesta,
|
|
2906
|
+
* porque esa fachada ocultaba el `selectorButton` cuando había icono (`selectorButton: "!hidden"`,
|
|
2907
|
+
* `git show b39d28f:src/components/autocomplete/AutoComplete.tsx`): el spinner se escondía con él.
|
|
2908
|
+
* Aquí se muestra SIEMPRE, en el sitio del extremo que haya (icono o chevron), porque una prop que
|
|
2909
|
+
* no pinta nada con la configuración por defecto es justo lo que la app reporta como roto.
|
|
2910
|
+
*/
|
|
2911
|
+
const SPINNER = (jsxRuntime.jsx(react.Spinner, { "aria-hidden": true, "data-slot": "autocomplete-spinner", size: "sm", color: "current", className: "text-default-400" }));
|
|
2165
2912
|
/**
|
|
2166
2913
|
* Clases del wrapper del icono: lo mete DENTRO del recuadro del campo.
|
|
2167
2914
|
*
|
|
@@ -2462,11 +3209,20 @@ const AuraAutocomplete = ({ label, id, icon, placeholder, className, children, v
|
|
|
2462
3209
|
// limpiarlo. Se reenvían tal cual al `ComboBox`, así que para el consumidor no cambia nada:
|
|
2463
3210
|
// pasar `undefined` es indistinguible de no pasarlos (react-aria decide controlado/no
|
|
2464
3211
|
// controlado con `!== undefined`).
|
|
2465
|
-
isClearable = true, isReadOnly, inputValue, selectedKey, onInputChange, onSelectionChange, translations, radius: _radius, ...props }) => {
|
|
3212
|
+
isClearable = true, isReadOnly, inputValue, selectedKey, onInputChange, onSelectionChange, translations, radius: _radius, isInvalid, errorMessage, listboxProps, isLoading = false, allowsCustomValue, allowsEmptyCollection, ...props }) => {
|
|
2466
3213
|
const generatedId = $8WNJc$react.useId();
|
|
2467
3214
|
const autoId = id || generatedId;
|
|
2468
|
-
|
|
3215
|
+
// Con `isLoading` el spinner ocupa el extremo (ver `SPINNER`): a efectos de maquetación es el
|
|
3216
|
+
// icono, así que todas las decisiones de hueco de abajo lo tratan como tal.
|
|
3217
|
+
const resolvedIcon = isLoading
|
|
3218
|
+
? SPINNER
|
|
3219
|
+
: icon === undefined
|
|
3220
|
+
? DEFAULT_ICON
|
|
3221
|
+
: icon;
|
|
2469
3222
|
const t = { ...DEFAULT_TRANSLATIONS$f, ...translations };
|
|
3223
|
+
// La regla de v2 para la lista vacía (ver `EMPTY_CONTENT_CLASS`).
|
|
3224
|
+
const hideEmptyContent = Boolean(allowsCustomValue) && listboxProps?.emptyContent == null;
|
|
3225
|
+
const emptyContent = listboxProps?.emptyContent ?? t.emptyContent;
|
|
2470
3226
|
/** El `<input>` real, para devolverle el foco tras limpiar (ver `handleClear`). */
|
|
2471
3227
|
const inputRef = $8WNJc$react.useRef(null);
|
|
2472
3228
|
// `data-visible` de v2, traducido a montar/no montar: `!!state.selectedItem ||
|
|
@@ -2502,7 +3258,7 @@ isClearable = true, isReadOnly, inputValue, selectedKey, onInputChange, onSelect
|
|
|
2502
3258
|
onInputChange?.("");
|
|
2503
3259
|
inputRef.current?.focus();
|
|
2504
3260
|
};
|
|
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: [
|
|
3261
|
+
return (jsxRuntime.jsxs(react.ComboBox, { id: autoId, isDisabled: isDisabled, isReadOnly: isReadOnly, inputValue: inputValue, selectedKey: selectedKey, onInputChange: onInputChange, onSelectionChange: onSelectionChange, isInvalid: isInvalid, allowsCustomValue: allowsCustomValue, allowsEmptyCollection: allowsEmptyCollection ?? !hideEmptyContent, className: [FIELD_STACK_CLASS$2, className].filter(Boolean).join(" "), ...props, children: [label && jsxRuntime.jsx(react.Label, { className: LABEL_CLASS$3, children: label }), jsxRuntime.jsxs(react.ComboBox.InputGroup, { className: [
|
|
2506
3262
|
SIZE_CLASS$2[size],
|
|
2507
3263
|
PADDING_CLASS$1[size],
|
|
2508
3264
|
variant === "underlined" ? UNDERLINED_INNER_CLASS[size] : "",
|
|
@@ -2565,7 +3321,13 @@ isClearable = true, isReadOnly, inputValue, selectedKey, onInputChange, onSelect
|
|
|
2565
3321
|
isDisabled ? ICON_DISABLED_CLASS : "",
|
|
2566
3322
|
]
|
|
2567
3323
|
.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,
|
|
3324
|
+
.join(" "), children: resolvedIcon })), jsxRuntime.jsx(react.ComboBox.Trigger, { className: resolvedIcon != null ? "hidden" : undefined })] }), isInvalid && errorMessage != null && errorMessage !== false && (jsxRuntime.jsx(react.FieldError, { className: ERROR_MESSAGE_CLASS, children: errorMessage })), jsxRuntime.jsx(react.ComboBox.Popover, { children: jsxRuntime.jsx(react.ListBox, { autoFocus: "first", className: OPCIONES_CLASS, renderEmptyState: hideEmptyContent
|
|
3325
|
+
? undefined
|
|
3326
|
+
: () => jsxRuntime.jsx("div", { className: EMPTY_CONTENT_CLASS$1, children: emptyContent }), children: withV2ItemContent(
|
|
3327
|
+
// El normalizador de `utils/` está tipado solo para `ReactNode`; con una
|
|
3328
|
+
// función la devuelve tal cual (`isValidElement` es falso), así que el cast
|
|
3329
|
+
// solo alinea el tipo con lo que ya pasa en ejecución.
|
|
3330
|
+
withCollectionItemIds(children)) }) })] }));
|
|
2569
3331
|
};
|
|
2570
3332
|
|
|
2571
3333
|
/**
|
|
@@ -8409,6 +9171,25 @@ const DAY_PERIOD_SEGMENT_CLASS = "ms-0.5";
|
|
|
8409
9171
|
* el texto (`rgb(243,18,96)`). Deuda a reportar aparte, no de este arreglo.
|
|
8410
9172
|
*/
|
|
8411
9173
|
const FIELD_FOCUS_CLASS$2 = "ring-0 ring-offset-0";
|
|
9174
|
+
/*
|
|
9175
|
+
* TECH-2459 · hallazgo beweossmbs 23-sep — `startContent`/`endContent` de v2, repuestas DENTRO de
|
|
9176
|
+
* la caja del campo.
|
|
9177
|
+
*
|
|
9178
|
+
* v2 (`@heroui/date-input@2.3.32`, `dist/time-input.js:319-329`) las pintaba en el `innerWrapper`,
|
|
9179
|
+
* dentro del `inputWrapper` (la caja con borde): `startContent`, los segmentos y `endContent`, en
|
|
9180
|
+
* ese orden. El `tv` de v2 (`@heroui/theme@2.4.15`, `date-input.js`) da a ese envoltorio
|
|
9181
|
+
* `flex items-center … w-full gap-x-2` y al `inputWrapper` `px-3 gap-3`. v1 no aceptaba las
|
|
9182
|
+
* props: el alta de citas de beweossmbs (`datetime-section.component.tsx`) pone ahí las flechas ±
|
|
9183
|
+
* de la rejilla de horas y desaparecían.
|
|
9184
|
+
*
|
|
9185
|
+
* En v3 el sitio equivalente son `TimeField.Prefix`/`TimeField.Suffix` (`DateInputGroupPrefix`/
|
|
9186
|
+
* `Suffix`), hijos del mismo `TimeField.Group`. Sus márgenes ya son los de v2 (`ms-3`/`me-3` = el
|
|
9187
|
+
* `px-3` del wrapper, y el input pasa a `pe-2`/`ps-2` = el `gap-x-2`), así que solo se añade una
|
|
9188
|
+
* cosa: `pointer-events-auto`. `.date-input-group__prefix/__suffix` de `@heroui/styles` los
|
|
9189
|
+
* declara `pointer-events-none` (pensados para un icono decorativo) y solo `.date-picker` los
|
|
9190
|
+
* reactiva; en un `TimeField` un botón puesto ahí no recibiría el clic. v2 no tenía esa regla.
|
|
9191
|
+
*/
|
|
9192
|
+
const CONTENT_SLOT_CLASS = "pointer-events-auto";
|
|
8412
9193
|
/**
|
|
8413
9194
|
* Implementación común de los dos nombres exportados. La única diferencia entre `TimeInput` y
|
|
8414
9195
|
* `TimeInputComponent` es el valor por defecto de `labelPlacement`; una `labelPlacement` explícita
|
|
@@ -8419,16 +9200,16 @@ function TimeInputBase({ label, id, className, labelPlacement,
|
|
|
8419
9200
|
// `dist/use-time-input.js:58`, `shouldForceLeadingZeros = true`), así que la referencia v2
|
|
8420
9201
|
// muestra "09:00 AM"; React Aria por sí solo deja "9:00 AM". Es una prop de React Aria que v3
|
|
8421
9202
|
// sigue aceptando (`AriaTimeFieldProps`), solo cambió el default.
|
|
8422
|
-
shouldForceLeadingZeros = true, ...props }) {
|
|
9203
|
+
shouldForceLeadingZeros = true, startContent, endContent, ...props }) {
|
|
8423
9204
|
const generatedId = $8WNJc$react.useId();
|
|
8424
9205
|
const autoId = id || generatedId;
|
|
8425
9206
|
return (jsxRuntime.jsxs(react.TimeField, { id: autoId,
|
|
8426
9207
|
// `cn` (tailwind-merge) y no una concatenación: el consumidor tiene que poder pisar la
|
|
8427
9208
|
// colocación y el ancho (`ScheduleRow` pasa `w-full`). Todas las clases de aquí son
|
|
8428
9209
|
// 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.
|
|
8430
|
-
|
|
8431
|
-
|
|
9210
|
+
className: react.cn(ROOT_CLASS[labelPlacement], className), shouldForceLeadingZeros: shouldForceLeadingZeros, ...props, children: [label && (jsxRuntime.jsx(react.Label, { className: LABEL_CLASS_BY_PLACEMENT[labelPlacement], children: label })), jsxRuntime.jsxs(react.TimeField.Group, { className: react.cn(FIELD_BORDER_CLASS, GROUP_SIZE_CLASS, FIELD_FOCUS_CLASS$2), children: [startContent !== undefined && startContent !== null && (jsxRuntime.jsx(react.TimeField.Prefix, { className: CONTENT_SLOT_CLASS, children: startContent })), jsxRuntime.jsx(react.TimeField.Input, { className: INPUT_CLASS, children: (segment) => (jsxRuntime.jsx(react.TimeField.Segment, { segment: segment, className: segment.type === "dayPeriod"
|
|
9211
|
+
? DAY_PERIOD_SEGMENT_CLASS
|
|
9212
|
+
: undefined })) }), endContent !== undefined && endContent !== null && (jsxRuntime.jsx(react.TimeField.Suffix, { className: CONTENT_SLOT_CLASS, children: endContent }))] })] }));
|
|
8432
9213
|
}
|
|
8433
9214
|
/**
|
|
8434
9215
|
* `TimeInput` — el nombre que en v2 era el crudo de HeroUI: etiqueta ENCIMA del campo
|
|
@@ -8798,6 +9579,33 @@ const SIZE_TEXT_CLASS$1 = {
|
|
|
8798
9579
|
// `min-w-0` es obligatorio: un ítem flex tiene `min-width: auto`, o sea no baja de su `min-content`,
|
|
8799
9580
|
// y sin eso el texto desborda el trigger en vez de truncar con elipsis.
|
|
8800
9581
|
const VALUE_ROOM_CLASS = "me-6 min-w-0 truncate";
|
|
9582
|
+
/*
|
|
9583
|
+
* `isMultiline` (v2). Literal del `tv` (`@heroui/theme@2.4.15`, `dist/components/select.js:255-263`):
|
|
9584
|
+
*
|
|
9585
|
+
* isMultiline: { true: { label: "relative", trigger: "!h-auto" }, false: { value: "truncate" } }
|
|
9586
|
+
*
|
|
9587
|
+
* O sea: el disparador deja de tener alto fijo y el valor deja de truncarse. El `min-h-*` del
|
|
9588
|
+
* tamaño se conserva (v2 también: el `!h-auto` solo pisaba el `h-*`), así que en una línea mide lo
|
|
9589
|
+
* mismo. El `py-1.5` es nuestro y es obligatorio aquí: `TRIGGER_ALIGN_CLASS` pone `py-0` porque con
|
|
9590
|
+
* alto fijo el centrado lo hace `items-center`; con alto automático, sin relleno vertical la
|
|
9591
|
+
* segunda línea tocaría el borde. 6px + 20px de línea + 6px = los 32px del `sm`, así que en una
|
|
9592
|
+
* sola línea no cambia nada. Van DESPUÉS en el `cn`, que resuelve `h-8`→`h-auto` y `py-0`→`py-1.5`.
|
|
9593
|
+
*/
|
|
9594
|
+
const MULTILINE_TRIGGER_CLASS = "h-auto py-1.5";
|
|
9595
|
+
const MULTILINE_VALUE_ROOM_CLASS = "me-6 min-w-0 whitespace-normal";
|
|
9596
|
+
/*
|
|
9597
|
+
* `startContent` del disparador (v2). En v2 iba dentro del `innerWrapper`, delante del valor, con
|
|
9598
|
+
* el `gap-1.5` de ese slot (`select.js:100`). Aquí el disparador es el flex y el hueco va como
|
|
9599
|
+
* margen de la pieza, para no tocar el `gap` que v3 le pone al disparador entero.
|
|
9600
|
+
*/
|
|
9601
|
+
const START_CONTENT_CLASS = "me-1.5 flex shrink-0 items-center";
|
|
9602
|
+
/*
|
|
9603
|
+
* `isLoading` (v2): el spinner SUSTITUYE al chevron en el mismo sitio. Literales de v2
|
|
9604
|
+
* (`@heroui/select@2.4.33`, `use-select`: `getSpinnerProps` → `color: "current"`, `size: "sm"`;
|
|
9605
|
+
* slot `spinner: "absolute end-3"`). Se centra en vertical como el `Select.Indicator` de v3
|
|
9606
|
+
* (`inset-y-0 my-auto`) y toma el color del chevron (`text-field-placeholder`, `select.css`).
|
|
9607
|
+
*/
|
|
9608
|
+
const SPINNER_CLASS = "pointer-events-none absolute inset-y-0 end-3 my-auto text-field-placeholder";
|
|
8801
9609
|
/*
|
|
8802
9610
|
* EL ESTADO VACÍO DEL DESPLEGABLE (TECH-2459 · F5 · defecto reportado por el usuario y medido por
|
|
8803
9611
|
* la sesión de backoffice: «se pulsa el campo y no pasa nada»).
|
|
@@ -9134,15 +9942,24 @@ placeholder = "", selectionMode = "single", selectedKeys, defaultSelectedKeys, o
|
|
|
9134
9942
|
// `undefined` y el campo dejaba de llenar su contenedor: en una fila flex se encogía al ancho
|
|
9135
9943
|
// de su contenido (medido por S2: 208px dentro de una fila de 592, 336px de hueco). En una
|
|
9136
9944
|
// columna no se notaba porque el `stretch` del flex lo tapaba — pero afecta a cualquier app.
|
|
9137
|
-
fullWidth = true,
|
|
9945
|
+
fullWidth = true, isLoading = false,
|
|
9946
|
+
// Aceptada y descartada: ver `SelectProps.isVirtualized`.
|
|
9947
|
+
isVirtualized: _isVirtualized, disallowEmptySelection = false, isMultiline = false, renderValue, startContent, listboxProps, isOpen, defaultOpen, onOpenChange, className, classNames, translations = {}, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, "aria-details": ariaDetails, }) => {
|
|
9138
9948
|
const generatedId = $8WNJc$react.useId();
|
|
9139
9949
|
const selectId = id || generatedId;
|
|
9140
9950
|
const requiredField = isRequired;
|
|
9141
9951
|
const t = { ...DEFAULT_TRANSLATIONS$8, ...translations };
|
|
9952
|
+
// v2 pintaba `listboxProps.emptyContent` en el hueco de la lista vacía; gana a la traducción,
|
|
9953
|
+
// que es el default de la fachada (el de v2 era el literal inglés "No items.").
|
|
9954
|
+
const emptyContent = listboxProps?.emptyContent ?? t.emptyContent;
|
|
9955
|
+
// Estado propio SOLO para `multiple` + `disallowEmptySelection` sin controlar: para rechazar el
|
|
9956
|
+
// cambio que vaciaría la selección, la fachada tiene que ser dueña del valor. En los demás casos
|
|
9957
|
+
// no se lee (el hook va fuera de la condición por las reglas de hooks).
|
|
9958
|
+
const [ownMultiple, setOwnMultiple] = $8WNJc$react.useState(() => toArray(defaultSelectedKeys) ?? []);
|
|
9142
9959
|
// Mismo criterio que el `group-data-[has-helper=true]` de v2: cuenta como "con ayuda" el campo
|
|
9143
9960
|
// que efectivamente PINTA una descripción o un mensaje de error, no el que solo recibe la prop.
|
|
9144
9961
|
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],
|
|
9962
|
+
const triggerClassName = react.cn(SIZE_CLASS[size], radius === "full" ? PADDING_FULL_CLASS[size] : PADDING_CLASS[size], TRIGGER_ALIGN_CLASS, isMultiline ? MULTILINE_TRIGGER_CLASS : "", RADIUS_CLASS$4[radius], VARIANT_CLASS$1[variant],
|
|
9146
9963
|
// En v2 el estado inválido MANDABA también con el campo enfocado
|
|
9147
9964
|
// (`group-data-[focus=true]:!border-danger`), así que el borde de foco no se aplica cuando
|
|
9148
9965
|
// el campo está en error: se queda el `danger`.
|
|
@@ -9155,7 +9972,32 @@ fullWidth = true, isOpen, defaultOpen, onOpenChange, className, classNames, tran
|
|
|
9155
9972
|
"w-full", classNames?.trigger);
|
|
9156
9973
|
const inner = (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [label && (jsxRuntime.jsx(react.Label, { className: react.cn(LABEL_CLASS$1, hasHelper
|
|
9157
9974
|
? 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: [
|
|
9975
|
+
: LABEL_OFFSET_CLASS[size], isInvalid ? INVALID_LABEL_CLASS$1 : "", classNames?.label), children: label })), jsxRuntime.jsxs(react.Select.Trigger, { className: triggerClassName, children: [startContent != null && startContent !== false && (jsxRuntime.jsx("span", { "data-slot": "start-content", className: START_CONTENT_CLASS, children: startContent })), jsxRuntime.jsx(react.Select.Value, { className: react.cn(SIZE_TEXT_CLASS$1[size], isMultiline ? MULTILINE_VALUE_ROOM_CLASS : VALUE_ROOM_CLASS, HIDE_ITEM_EXTRAS_CLASS, isInvalid ? INVALID_TEXT_CLASS$1 : ""), children: renderValue
|
|
9976
|
+
? // `renderValue` de v2 (`chunk-BJWIL32D.mjs`): sin selección, el placeholder;
|
|
9977
|
+
// con selección, la función con los items en la forma de v2. El nodo de la
|
|
9978
|
+
// colección de v3 trae los mismos campos que el de v2, así que el mapeo es
|
|
9979
|
+
// 1:1. `defaultChildren` es aquí el placeholder (o `""`, ver arriba).
|
|
9980
|
+
({ state, isPlaceholder, defaultChildren }) => {
|
|
9981
|
+
if (isPlaceholder) {
|
|
9982
|
+
return defaultChildren;
|
|
9983
|
+
}
|
|
9984
|
+
const items = state.selectedItems.map((item) => ({
|
|
9985
|
+
key: item.key,
|
|
9986
|
+
data: item.value,
|
|
9987
|
+
type: item.type,
|
|
9988
|
+
props: item.props,
|
|
9989
|
+
textValue: item.textValue,
|
|
9990
|
+
rendered: item.rendered,
|
|
9991
|
+
"aria-label": item["aria-label"],
|
|
9992
|
+
}));
|
|
9993
|
+
return renderValue(items);
|
|
9994
|
+
}
|
|
9995
|
+
: undefined }), isLoading ? (jsxRuntime.jsx(react.Spinner, { "aria-hidden": true, "data-slot": "select-spinner", size: "sm", color: "current", className: SPINNER_CLASS })) : (
|
|
9996
|
+
/* `.select__indicator` de v3 va a `end-2` (8px del borde interior); el
|
|
9997
|
+
`selectorIcon` de v2 iba a `end-3` (12px). Medido en los PNG: la tinta del chevron
|
|
9998
|
+
acaba a 17px del borde derecho en la referencia y a 13px con el valor de v3 — 4px
|
|
9999
|
+
de más. */
|
|
10000
|
+
jsxRuntime.jsx(react.Select.Indicator, { className: "end-3" }))] }), description && !isInvalid && (jsxRuntime.jsx(react.Description, { className: `${HELPER_CLASS$1} text-left`, children: description })), jsxRuntime.jsx(react.Select.Popover, { className: classNames?.popoverContent, children: jsxRuntime.jsx(react.ListBox
|
|
9159
10001
|
// El hueco que v2 llenaba con su `emptyContent` (ver el bloque de
|
|
9160
10002
|
// `DEFAULT_TRANSLATIONS`). `renderEmptyState` es la vía de v3: el `ListBox` de
|
|
9161
10003
|
// react-aria solo pinta algo con la colección vacía si se le pasa, y envuelve
|
|
@@ -9169,7 +10011,7 @@ fullWidth = true, isOpen, defaultOpen, onOpenChange, className, classNames, tran
|
|
|
9169
10011
|
// el resultado en un `<div role="option" style="display:contents">`
|
|
9170
10012
|
// (`react-aria-components/dist/private/ListBox.mjs:222-228`), o sea que la caja
|
|
9171
10013
|
// que se ve es la nuestra.
|
|
9172
|
-
renderEmptyState: () => (jsxRuntime.jsx("div", { className: EMPTY_CONTENT_CLASS, children:
|
|
10014
|
+
renderEmptyState: () => (jsxRuntime.jsx("div", { className: EMPTY_CONTENT_CLASS, children: emptyContent })), children: withItemIndicator(withV2ItemContent(withCollectionItemIds(children))) }) }), isInvalid && errorMessage && (jsxRuntime.jsx(react.FieldError, { className: `${HELPER_CLASS$1} text-left`, children: errorMessage }))] }));
|
|
9173
10015
|
const commonProps = {
|
|
9174
10016
|
id: selectId,
|
|
9175
10017
|
name,
|
|
@@ -9211,7 +10053,20 @@ fullWidth = true, isOpen, defaultOpen, onOpenChange, className, classNames, tran
|
|
|
9211
10053
|
fullWidth ? "w-full" : ROOT_MIN_WIDTH_CLASS, className),
|
|
9212
10054
|
};
|
|
9213
10055
|
if (selectionMode === "multiple") {
|
|
9214
|
-
|
|
10056
|
+
// `disallowEmptySelection` en `multiple`: react-stately lo fuerza a `false` en este modo
|
|
10057
|
+
// (`useSelectState.mjs:61`, `disallowEmptySelection: selectionMode === 'single'`, pisando
|
|
10058
|
+
// la prop), así que la guarda la pone la fachada: el cambio que dejaría la selección vacía
|
|
10059
|
+
// no se propaga. Controlado basta con no avisar; sin controlar, la fachada lleva el valor.
|
|
10060
|
+
const ownsValue = disallowEmptySelection && selectedKeys === undefined;
|
|
10061
|
+
return (jsxRuntime.jsx(react.Select, { ...commonProps, selectionMode: "multiple", value: ownsValue ? ownMultiple : toArray(selectedKeys), defaultValue: ownsValue ? undefined : toArray(defaultSelectedKeys), onChange: (keys) => {
|
|
10062
|
+
if (disallowEmptySelection && keys.length === 0) {
|
|
10063
|
+
return;
|
|
10064
|
+
}
|
|
10065
|
+
if (ownsValue) {
|
|
10066
|
+
setOwnMultiple(keys);
|
|
10067
|
+
}
|
|
10068
|
+
onSelectionChange?.(new Set(keys));
|
|
10069
|
+
}, children: inner }));
|
|
9215
10070
|
}
|
|
9216
10071
|
// `Array.from` cubre las tres formas de `SelectSelection` sin ramas nuevas: `"all"` sigue
|
|
9217
10072
|
// cayendo a `null` (v3 no admite "todo" en selección única) y un array se lee igual que un
|
|
@@ -9345,59 +10200,491 @@ const StatTile = ({ title, value, icon, trend, translations = {}, className, pad
|
|
|
9345
10200
|
.join(" "), ...cardProps, children: [icon && (jsxRuntime.jsx("span", { className: "shrink-0 text-foreground-600", "aria-hidden": "true", children: icon })), jsxRuntime.jsxs("div", { className: "flex min-w-0 flex-col gap-0.5", children: [jsxRuntime.jsx(P, { className: "truncate", children: title }), jsxRuntime.jsxs("div", { className: "flex items-baseline gap-2", children: [jsxRuntime.jsx(H3, { className: "text-2xl font-semibold", children: value }), trend && (jsxRuntime.jsx("span", { className: `text-xs font-medium ${TREND_COLOR_CLASS[direction]}`, "aria-label": t[TREND_ARIA_KEY[direction]], children: trend.value }))] })] })] }));
|
|
9346
10201
|
};
|
|
9347
10202
|
|
|
10203
|
+
// TECH-2459 — Fachada `Checkbox` (hallazgo loginos 07-sep: `index.tsx` reexportaba el `Checkbox`
|
|
10204
|
+
// CRUDO de v3, así que el JSX v2 de las apps — `isSelected` + `onValueChange` dentro de `Controller`
|
|
10205
|
+
// de RHF en backoffice ×12, smbs ×23, agency ×5 — no compilaba pese a existir el símbolo).
|
|
10206
|
+
//
|
|
10207
|
+
// HeroUI v3 convierte el Checkbox en compound (`Checkbox.Content` > `Checkbox.Control` >
|
|
10208
|
+
// `Checkbox.Indicator` + la etiqueta como children de `Content`), renombra `onValueChange` →
|
|
10209
|
+
// `onChange` y ELIMINA `color`, `size`, `radius`, `lineThrough`, `icon`, `classNames` y
|
|
10210
|
+
// `disableAnimation` (guía oficial `checkbox`). Su único eje propio es `variant`
|
|
10211
|
+
// (`primary`/`secondary`), que NO es un color: `secondary` es un tratamiento gris de menor énfasis
|
|
10212
|
+
// — el falso amigo de siempre (skill `auraui-design-tokens`).
|
|
10213
|
+
//
|
|
10214
|
+
// Esta fachada compone el compound por dentro y repone cada prop v2 con los literales del `tv`
|
|
10215
|
+
// de v2 (`@heroui/theme@2.4.15` → `dist/components/checkbox.js`), traducidos a utilidades de
|
|
10216
|
+
// Tailwind 4 sobre los tokens de Aurora. El relleno del estado marcado en v3 es el `::before` del
|
|
10217
|
+
// control (`bg-accent`, = `--color-primary` vía F1), así que `color` se reimplementa pisando ese
|
|
10218
|
+
// pseudo-elemento con `before:bg-<color>` — utilidad (capa `utilities`) que gana a la regla de
|
|
10219
|
+
// `checkbox.css` (capa `components`), incluida la de hover (`bg-accent-hover`).
|
|
10220
|
+
//
|
|
10221
|
+
// ⚠️ Estados de react-aria, verificados en el navegador contra `checkbox.css`: `data-selected`/
|
|
10222
|
+
// `data-indeterminate`/`data-invalid`/`data-disabled` viven en la RAÍZ (`.checkbox`);
|
|
10223
|
+
// `data-hovered`/`data-pressed`/`data-focus-visible` en `[data-slot="checkbox-content"]`. Por
|
|
10224
|
+
// eso hay dos grupos con nombre (`group/checkbox`, `group/content`) y NUNCA `data-[hover=true]`
|
|
10225
|
+
// (trampa 3 de la skill: v2 escribía `data-hover`; react-aria escribe `data-hovered`).
|
|
9348
10226
|
/**
|
|
9349
|
-
*
|
|
9350
|
-
*
|
|
9351
|
-
|
|
9352
|
-
|
|
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.
|
|
10227
|
+
* Relleno del estado marcado (`::before` del control) y del indeterminado (fondo del control)
|
|
10228
|
+
* por color. `primary` no pisa nada: el `bg-accent` de v3 YA es `--color-primary`. `default`
|
|
10229
|
+
* usa el gris de Aurora (`--color-default`, = v2), nunca el `--default` crudo de v3 (D-F1-3).
|
|
10230
|
+
*
|
|
10231
|
+
* Literal v2: `after:bg-<color> after:text-<color>-foreground text-<color>-foreground`.
|
|
9358
10232
|
*/
|
|
9359
|
-
const
|
|
9360
|
-
|
|
9361
|
-
|
|
9362
|
-
]
|
|
9363
|
-
|
|
9364
|
-
|
|
9365
|
-
|
|
9366
|
-
}
|
|
9367
|
-
const TableFacade = ({ children, ...props }) => {
|
|
9368
|
-
const nodes = $8WNJc$react.Children.toArray(children);
|
|
9369
|
-
// Sin hijos, o con un contenedor de scroll ya montado a mano: el root tal cual.
|
|
9370
|
-
if (nodes.length === 0 || nodes.some((node) => isPart$2(node, OWN_SCROLL))) {
|
|
9371
|
-
return jsxRuntime.jsx(react.Table, { ...props, children: children });
|
|
9372
|
-
}
|
|
9373
|
-
const scrollable = nodes.filter((node) => !isPart$2(node, OUTSIDE_SCROLL));
|
|
9374
|
-
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] }));
|
|
10233
|
+
const CONTROL_COLOR = {
|
|
10234
|
+
primary: "",
|
|
10235
|
+
default: "before:bg-default group-data-[indeterminate=true]/checkbox:bg-default",
|
|
10236
|
+
secondary: "before:bg-secondary group-data-[indeterminate=true]/checkbox:bg-secondary",
|
|
10237
|
+
success: "before:bg-success group-data-[indeterminate=true]/checkbox:bg-success",
|
|
10238
|
+
warning: "before:bg-warning group-data-[indeterminate=true]/checkbox:bg-warning",
|
|
10239
|
+
danger: "before:bg-danger group-data-[indeterminate=true]/checkbox:bg-danger",
|
|
9376
10240
|
};
|
|
9377
|
-
TableFacade.displayName = "Aurora.Table";
|
|
9378
10241
|
/**
|
|
9379
|
-
*
|
|
9380
|
-
*
|
|
9381
|
-
* `Table.Content`/`Table.Header`/`isRowHeader`… se comportan igual que antes.
|
|
9382
|
-
*
|
|
9383
|
-
* `Table.Root` queda deliberadamente crudo (el root pelado de v3, sin scroll): es la vía de escape
|
|
9384
|
-
* para quien quiera montar la anatomía entera a mano.
|
|
10242
|
+
* Color del glifo (check / línea). v3 lo fija en el SVG (`[data-slot="checkbox-default-indicator--
|
|
10243
|
+
* checkmark"] { text-accent-foreground }`), así que hay que alcanzar al SVG, no al indicador.
|
|
9385
10244
|
*/
|
|
9386
|
-
const
|
|
9387
|
-
|
|
9388
|
-
|
|
9389
|
-
|
|
9390
|
-
|
|
9391
|
-
|
|
9392
|
-
|
|
9393
|
-
|
|
9394
|
-
|
|
9395
|
-
|
|
9396
|
-
|
|
9397
|
-
|
|
9398
|
-
|
|
9399
|
-
|
|
9400
|
-
|
|
10245
|
+
const INDICATOR_COLOR = {
|
|
10246
|
+
primary: "",
|
|
10247
|
+
default: "[&_svg]:text-default-foreground",
|
|
10248
|
+
secondary: "[&_svg]:text-secondary-foreground",
|
|
10249
|
+
success: "[&_svg]:text-success-foreground",
|
|
10250
|
+
warning: "[&_svg]:text-warning-foreground",
|
|
10251
|
+
danger: "[&_svg]:text-danger-foreground",
|
|
10252
|
+
};
|
|
10253
|
+
// v2 (`checkbox.js`, variante `size`): wrapper `w-4/w-5/w-6` (16/20/24px), label
|
|
10254
|
+
// `text-small/medium/large`, icon `w-3 h-2` / `w-4 h-3` / `w-5 h-4`. v3: control `size-4`, glifo
|
|
10255
|
+
// `size-2.5`, texto `text-sm`, para todos.
|
|
10256
|
+
const CONTROL_SIZE = {
|
|
10257
|
+
sm: "size-4",
|
|
10258
|
+
md: "size-5",
|
|
10259
|
+
lg: "size-6",
|
|
10260
|
+
};
|
|
10261
|
+
const INDICATOR_SIZE = {
|
|
10262
|
+
sm: "size-3 [&_svg]:size-2.5",
|
|
10263
|
+
md: "size-3.5 [&_svg]:size-3",
|
|
10264
|
+
lg: "size-4 [&_svg]:size-3.5",
|
|
10265
|
+
};
|
|
10266
|
+
const LABEL_SIZE = {
|
|
10267
|
+
sm: "text-sm",
|
|
10268
|
+
md: "text-base",
|
|
10269
|
+
lg: "text-lg",
|
|
10270
|
+
};
|
|
10271
|
+
// v2: `rounded-[calc(theme(borderRadius.medium)*K)]` con `medium` = 12px → 6 / 7.2 / 8.4 px, y por
|
|
10272
|
+
// defecto el radio lo decidía `size` (sm→0.5, md→0.6, lg→0.7). El `::before` (relleno) lleva el
|
|
10273
|
+
// mismo radio porque aquí cubre el border-box completo (ver `CONTROL_BASE`).
|
|
10274
|
+
const RADIUS_CLASS$3 = {
|
|
10275
|
+
none: "rounded-none before:rounded-none",
|
|
10276
|
+
sm: "rounded-[6px] before:rounded-[6px]",
|
|
10277
|
+
md: "rounded-[7.2px] before:rounded-[7.2px]",
|
|
10278
|
+
lg: "rounded-[8.4px] before:rounded-[8.4px]",
|
|
10279
|
+
full: "rounded-full before:rounded-full",
|
|
10280
|
+
};
|
|
10281
|
+
const RADIUS_BY_SIZE = {
|
|
10282
|
+
sm: "sm",
|
|
10283
|
+
md: "md",
|
|
10284
|
+
lg: "lg",
|
|
10285
|
+
};
|
|
10286
|
+
/**
|
|
10287
|
+
* Caja del control (slot `wrapper` de v2): borde de 2px en `--color-default` sin sombra
|
|
10288
|
+
* (`before:border-2 before:border-default`), y el relleno cubriendo la caja ENTERA al marcarse.
|
|
10289
|
+
*
|
|
10290
|
+
* En v3 el borde vive en el propio control (1px, `border-field-border`, `shadow-field`) y el
|
|
10291
|
+
* relleno es un `::before` a `inset-0`, que solo alcanza al padding-box: por eso v3 pone
|
|
10292
|
+
* `border-transparent` al marcarse y el relleno queda 1px por dentro. Con el borde de 2px de v2
|
|
10293
|
+
* eso dejaría un anillo de 2px: se lleva el `::before` al border-box (`-inset-0.5` = −2px) y se
|
|
10294
|
+
* quita el `overflow-hidden` de v3 para que no lo recorte. El `::before` es un descendiente
|
|
10295
|
+
* posicionado, así que pinta ENCIMA del borde y lo tapa, como el `after` de v2.
|
|
10296
|
+
*
|
|
10297
|
+
* Hover v2: `group-data-[hover=true]:before:bg-default-100` — tinte suave de la caja sin marcar
|
|
10298
|
+
* (en v3 la caja es el fondo del control, no un pseudo). `data-hovered` lo emite `Checkbox.Content`.
|
|
10299
|
+
*
|
|
10300
|
+
* Indeterminado: v3 pinta el fondo del control (dentro del borde). Se enciende también el
|
|
10301
|
+
* `::before` para que el relleno cubra la caja entera, igual que al marcar.
|
|
10302
|
+
*/
|
|
10303
|
+
const CONTROL_BASE = [
|
|
10304
|
+
"border-2 border-default shadow-none overflow-visible",
|
|
10305
|
+
"before:-inset-0.5",
|
|
10306
|
+
"group-data-[hovered=true]/content:bg-default-100",
|
|
10307
|
+
"group-data-[indeterminate=true]/checkbox:before:scale-100",
|
|
10308
|
+
"group-data-[indeterminate=true]/checkbox:before:opacity-100",
|
|
10309
|
+
].join(" ");
|
|
10310
|
+
// v2 (`lineThrough: true`, slot `label`): la raya crece de 0 al 100% al marcarse y la etiqueta se
|
|
10311
|
+
// atenúa al 60%. `data-selected` está en la raíz → `group-data-[…]/checkbox`.
|
|
10312
|
+
const LINE_THROUGH = [
|
|
10313
|
+
"inline-flex items-center justify-center",
|
|
10314
|
+
"before:absolute before:h-0.5 before:w-0 before:bg-foreground before:content-['']",
|
|
10315
|
+
"group-data-[selected=true]/checkbox:opacity-60",
|
|
10316
|
+
"group-data-[selected=true]/checkbox:before:w-full",
|
|
10317
|
+
].join(" ");
|
|
10318
|
+
// Los subcomponentes compound de v3, adjuntos como estáticos para que la API v3 siga funcionando
|
|
10319
|
+
// a través de la fachada (`<Checkbox><Checkbox.Control>…`).
|
|
10320
|
+
const COMPOUND_PARTS$1 = new Set([
|
|
10321
|
+
react.Checkbox.Control,
|
|
10322
|
+
react.Checkbox.Indicator,
|
|
10323
|
+
react.Checkbox.Content,
|
|
10324
|
+
]);
|
|
10325
|
+
/** API compound de v3 detectada por hijos directos: la fachada no envuelve nada. */
|
|
10326
|
+
function hasCompoundChildren$1(children) {
|
|
10327
|
+
return $8WNJc$react.Children.toArray(children).some((child) => $8WNJc$react.isValidElement(child) && COMPOUND_PARTS$1.has(child.type));
|
|
10328
|
+
}
|
|
10329
|
+
/**
|
|
10330
|
+
* Checkbox - Casilla basada en HeroUI v3 con la API pública de v2 intacta (TECH-2459).
|
|
10331
|
+
*
|
|
10332
|
+
* - **API v2**: `children` como etiqueta, `isSelected`/`defaultSelected` + `onValueChange`
|
|
10333
|
+
* (también `onChange`), `color`, `size`, `radius`, `lineThrough`, `icon`, `classNames`. Funciona
|
|
10334
|
+
* dentro de `Controller` de react-hook-form (`isSelected={value} onValueChange={onChange}`).
|
|
10335
|
+
* - **API compound v3**: si los children traen `Checkbox.Control`/`Indicator`/`Content`, pasa tal
|
|
10336
|
+
* cual a la raíz de HeroUI (con `onValueChange` seguido igualmente).
|
|
10337
|
+
*/
|
|
10338
|
+
const CheckboxFacade = ({ children, onChange, onValueChange, color = "primary", size = "md", radius, lineThrough = false, icon, disableAnimation = false, isInvalid, classNames, className, ...rest }) => {
|
|
10339
|
+
const handleChange = (isSelected) => {
|
|
10340
|
+
onChange?.(isSelected);
|
|
10341
|
+
onValueChange?.(isSelected);
|
|
10342
|
+
};
|
|
10343
|
+
if (hasCompoundChildren$1(children)) {
|
|
10344
|
+
return (jsxRuntime.jsx(react.Checkbox, { ...rest, isInvalid: isInvalid, onChange: handleChange, className: react.cn(classNames?.base, className), children: children }));
|
|
10345
|
+
}
|
|
10346
|
+
const indicatorContent = typeof icon === "function"
|
|
10347
|
+
? (state) => icon({
|
|
10348
|
+
isSelected: state.isSelected,
|
|
10349
|
+
isIndeterminate: state.isIndeterminate,
|
|
10350
|
+
isDisabled: state.isDisabled,
|
|
10351
|
+
})
|
|
10352
|
+
: icon;
|
|
10353
|
+
const hasLabel = children !== undefined && children !== null && children !== false;
|
|
10354
|
+
return (jsxRuntime.jsx(react.Checkbox, { ...rest, isInvalid: isInvalid, onChange: handleChange,
|
|
10355
|
+
// Base v2: `group relative max-w-fit inline-flex items-center justify-start cursor-pointer
|
|
10356
|
+
// p-2 -m-2 select-none` (el `p-2 -m-2` es el área de pulsado ampliada de v2). v3
|
|
10357
|
+
// (`.checkbox`) usa `flex flex-col items-start` para apilar `Description`/`FieldError`,
|
|
10358
|
+
// slots que esta fachada no renderiza; en **inline** la caja participa de la línea de
|
|
10359
|
+
// texto, donde la esperan los consumidores (mismo criterio que la fachada `Switch`).
|
|
10360
|
+
className: react.cn("group/checkbox relative -m-2 inline-flex max-w-fit flex-row items-center p-2", classNames?.base, className), children: jsxRuntime.jsxs(react.Checkbox.Content, { className: "group/content gap-2 font-normal", children: [jsxRuntime.jsx(react.Checkbox.Control, { className: react.cn(CONTROL_BASE, CONTROL_SIZE[size], RADIUS_CLASS$3[radius ?? RADIUS_BY_SIZE[size]], CONTROL_COLOR[color],
|
|
10361
|
+
// v2 `isInvalid`: `before:border-danger` (el borde vivía en el pseudo).
|
|
10362
|
+
isInvalid && "border-danger", disableAnimation && "transition-none before:transition-none", classNames?.wrapper), children: jsxRuntime.jsx(react.Checkbox.Indicator, { className: react.cn(INDICATOR_SIZE[size], INDICATOR_COLOR[color], disableAnimation && "[&_svg]:transition-none", classNames?.icon), children: indicatorContent }) }), hasLabel && (
|
|
10363
|
+
// Slot `label` de v2: `relative text-foreground select-none` + tamaño de texto.
|
|
10364
|
+
// `data-slot="label"` para que las reglas de cursor/selección de `.checkbox` de v3
|
|
10365
|
+
// le alcancen igual que a un `<Label>`.
|
|
10366
|
+
jsxRuntime.jsx("span", { "data-slot": "label", className: react.cn("relative select-none text-foreground", LABEL_SIZE[size], lineThrough && LINE_THROUGH, isInvalid && "text-danger", classNames?.label), children: children }))] }) }));
|
|
10367
|
+
};
|
|
10368
|
+
CheckboxFacade.displayName = "Aurora.Checkbox";
|
|
10369
|
+
/**
|
|
10370
|
+
* `Checkbox` de Aurora: fachada v2 + estáticos compound de v3 (`Checkbox.Control`,
|
|
10371
|
+
* `Checkbox.Indicator`, `Checkbox.Content`), los mismos objetos que exporta `@heroui/react`.
|
|
10372
|
+
*/
|
|
10373
|
+
const Checkbox = Object.assign(CheckboxFacade, {
|
|
10374
|
+
Control: react.Checkbox.Control,
|
|
10375
|
+
Indicator: react.Checkbox.Indicator,
|
|
10376
|
+
Content: react.Checkbox.Content,
|
|
10377
|
+
});
|
|
10378
|
+
|
|
10379
|
+
// TECH-2459 — Piezas que la fachada `Table.Content` inserta para reponer la API v2 que v3 dejó en
|
|
10380
|
+
// manos de la app (defectos reportados por beweossmbs, 23-sep). Privadas: NO se reexportan desde
|
|
10381
|
+
// `_internal/index.ts` ni desde el `index.ts` de la carpeta.
|
|
10382
|
+
//
|
|
10383
|
+
// Todo se decide por IDENTIDAD de `child.type` contra los objetos reales de HeroUI (mismo patrón
|
|
10384
|
+
// que las fachadas `Alert`, `Tabs` y el propio root de `Table`), nunca por `displayName`.
|
|
10385
|
+
/**
|
|
10386
|
+
* `isHeaderSticky` declarado en el ROOT (`<Table isHeaderSticky>`, que es donde lo ponía v2) viaja
|
|
10387
|
+
* hasta `Table.Content` por contexto: el `<thead>` lo pinta el contenido, no el root.
|
|
10388
|
+
*/
|
|
10389
|
+
const TableStickyContext = $8WNJc$react.createContext(undefined);
|
|
10390
|
+
/**
|
|
10391
|
+
* Clases de v2 para `isHeaderSticky` (`@heroui/theme@2.4.15` → `dist/components/table.js`,
|
|
10392
|
+
* variante `isHeaderSticky.true.thead`): `sticky top-0 z-20 [&>tr]:first:shadow-small`. La sombra
|
|
10393
|
+
* se descarta: `shadow-small` es una clase del plugin de v2 que en Tailwind 4 no genera nada, y
|
|
10394
|
+
* v3 separa la cabecera del cuerpo con su propio borde (`.table__header { border-b }`).
|
|
10395
|
+
*
|
|
10396
|
+
* El «contenedor con scroll» respecto al que se pega es el `Table.ScrollContainer` que inserta la
|
|
10397
|
+
* fachada del root (`overflow-x: auto` ⇒ `overflow-y` computa `auto`), exactamente como en v2 lo
|
|
10398
|
+
* era el `wrapper` (`overflow-auto`): la cabecera se queda fija cuando ese contenedor tiene alto
|
|
10399
|
+
* acotado y desplaza en vertical.
|
|
10400
|
+
*/
|
|
10401
|
+
const STICKY_HEADER_CLASS = "sticky top-0 z-20";
|
|
10402
|
+
/** Clave fija de la columna/celda de selección insertadas (no colisiona con claves de negocio). */
|
|
10403
|
+
const SELECTION_KEY = "__aurora-selection";
|
|
10404
|
+
/**
|
|
10405
|
+
* Columna de «seleccionar todo». El `Checkbox slot="selection"` recibe de react-aria, por
|
|
10406
|
+
* `CheckboxContext`, el estado de la tabla: marcado con todo seleccionado, INDETERMINADO con una
|
|
10407
|
+
* selección parcial, y su `onChange` selecciona/deselecciona todo — `onSelectionChange` recibe
|
|
10408
|
+
* `"all"`, igual que en v2 (`useTableSelectAllCheckbox`). La etiqueta accesible también la pone
|
|
10409
|
+
* react-aria, traducida a la locale activa: por eso esta pieza no tiene texto propio ni
|
|
10410
|
+
* `translations`.
|
|
10411
|
+
*
|
|
10412
|
+
* `pe-0` es lo que hace la anatomía oficial de v3 con su columna de selección: la casilla no
|
|
10413
|
+
* necesita el padding final de una columna de texto.
|
|
10414
|
+
*/
|
|
10415
|
+
function SelectionColumn() {
|
|
10416
|
+
return (jsxRuntime.jsx(react.Table.Column, { className: "pe-0", id: SELECTION_KEY, children: jsxRuntime.jsx(Checkbox, { slot: "selection" }) }));
|
|
10417
|
+
}
|
|
10418
|
+
/** Casilla por fila (`useTableSelectionCheckbox` vía `CheckboxContext`, como en v2). */
|
|
10419
|
+
function SelectionCell() {
|
|
10420
|
+
return (jsxRuntime.jsx(react.Table.Cell, { className: "pe-0", children: jsxRuntime.jsx(Checkbox, { slot: "selection" }) }));
|
|
10421
|
+
}
|
|
10422
|
+
function isElementOf(node, type) {
|
|
10423
|
+
return $8WNJc$react.isValidElement(node) && node.type === type;
|
|
10424
|
+
}
|
|
10425
|
+
/**
|
|
10426
|
+
* Hijos listos para pasarse como argumentos VARIÁDICOS de `cloneElement`/`createElement`. Un array
|
|
10427
|
+
* pasado como argumento único es para React una LISTA y exige `key` en cada elemento: si la app
|
|
10428
|
+
* escribió hijos JSX sueltos (sin clave, legal) y la fachada los reenviara como un solo array,
|
|
10429
|
+
* saltaría «Each child in a list should have a unique key» (medido en jsdom). Esparcidos, React
|
|
10430
|
+
* los trata como los trató al escribirlos.
|
|
10431
|
+
*/
|
|
10432
|
+
function spread(node) {
|
|
10433
|
+
return Array.isArray(node) ? node : [node];
|
|
10434
|
+
}
|
|
10435
|
+
/**
|
|
10436
|
+
* Aplica `fn` a cada nodo de un árbol de `children` atravesando arrays y Fragments, sin tocar
|
|
10437
|
+
* claves (`cloneElement` las conserva). `Children.map` no sirve aquí: re-prefija las claves y NO
|
|
10438
|
+
* entra en Fragments — y las apps envuelven en Fragment cuando el contenido es condicional.
|
|
10439
|
+
*/
|
|
10440
|
+
function mapNodes(node, fn) {
|
|
10441
|
+
if (Array.isArray(node)) {
|
|
10442
|
+
return node.map((child) => mapNodes(child, fn));
|
|
10443
|
+
}
|
|
10444
|
+
if (isElementOf(node, $8WNJc$react.Fragment)) {
|
|
10445
|
+
return $8WNJc$react.cloneElement(node, undefined, ...spread(mapNodes(node.props.children, fn)));
|
|
10446
|
+
}
|
|
10447
|
+
return fn(node);
|
|
10448
|
+
}
|
|
10449
|
+
/**
|
|
10450
|
+
* ¿Hay ya una casilla de selección montada (por la fachada o por la app) en este subárbol
|
|
10451
|
+
* estático? Mira la identidad de las piezas propias y cualquier elemento con `slot="selection"`
|
|
10452
|
+
* (la forma que documenta v3). Las funciones de render no se pueden inspeccionar sin llamarlas:
|
|
10453
|
+
* esas cuentan como «no hay».
|
|
10454
|
+
*/
|
|
10455
|
+
function hasSelectionControl(node) {
|
|
10456
|
+
if (Array.isArray(node)) {
|
|
10457
|
+
return node.some(hasSelectionControl);
|
|
10458
|
+
}
|
|
10459
|
+
if (!$8WNJc$react.isValidElement(node)) {
|
|
10460
|
+
return false;
|
|
10461
|
+
}
|
|
10462
|
+
if (node.type === SelectionColumn || node.type === SelectionCell) {
|
|
10463
|
+
return true;
|
|
10464
|
+
}
|
|
10465
|
+
const props = node.props;
|
|
10466
|
+
if (props.slot === "selection") {
|
|
10467
|
+
return true;
|
|
10468
|
+
}
|
|
10469
|
+
return typeof props.children === "function"
|
|
10470
|
+
? false
|
|
10471
|
+
: hasSelectionControl(props.children);
|
|
10472
|
+
}
|
|
10473
|
+
function containsType(node, type) {
|
|
10474
|
+
if (Array.isArray(node)) {
|
|
10475
|
+
return node.some((child) => containsType(child, type));
|
|
10476
|
+
}
|
|
10477
|
+
if (!$8WNJc$react.isValidElement(node)) {
|
|
10478
|
+
return false;
|
|
10479
|
+
}
|
|
10480
|
+
if (node.type === type) {
|
|
10481
|
+
return true;
|
|
10482
|
+
}
|
|
10483
|
+
const children = node.props.children;
|
|
10484
|
+
return typeof children === "function" ? false : containsType(children, type);
|
|
10485
|
+
}
|
|
10486
|
+
/**
|
|
10487
|
+
* Columna con `allowsSorting`: v2 pintaba el chevron por su cuenta (`TableColumnHeader`,
|
|
10488
|
+
* `ChevronDownIcon` con `data-direction`); v3 no pinta nada y pide que la app monte
|
|
10489
|
+
* `Table.SortableColumnHeader` en el render prop de la columna. Aquí se monta esa pieza de v3
|
|
10490
|
+
* — aspecto de v3 (§9.1): chevron a la derecha que apunta arriba en ascendente y gira en
|
|
10491
|
+
* descendente, y sin chevron mientras la columna no es la ordenada.
|
|
10492
|
+
*
|
|
10493
|
+
* No se toca la columna si la app ya decide el contenido con un render prop (entonces es suyo) o
|
|
10494
|
+
* si ya montó un `SortableColumnHeader` estático.
|
|
10495
|
+
*/
|
|
10496
|
+
function withSortIndicator(node) {
|
|
10497
|
+
if (!isElementOf(node, react.Table.Column)) {
|
|
10498
|
+
return node;
|
|
10499
|
+
}
|
|
10500
|
+
const { allowsSorting, children } = node.props;
|
|
10501
|
+
if (!allowsSorting ||
|
|
10502
|
+
typeof children === "function" ||
|
|
10503
|
+
containsType(children, react.Table.SortableColumnHeader)) {
|
|
10504
|
+
return node;
|
|
10505
|
+
}
|
|
10506
|
+
return $8WNJc$react.cloneElement(node, {
|
|
10507
|
+
children: ({ sortDirection }) => (jsxRuntime.jsx(react.Table.SortableColumnHeader, { sortDirection: sortDirection, children: children })),
|
|
10508
|
+
});
|
|
10509
|
+
}
|
|
10510
|
+
/** Compone una clase sobre un `className` de react-aria, que puede ser string o render prop. */
|
|
10511
|
+
function withClass(className, extra) {
|
|
10512
|
+
if (typeof className === "function") {
|
|
10513
|
+
return (renderProps) => react.cn(className(renderProps), extra);
|
|
10514
|
+
}
|
|
10515
|
+
return react.cn(className, extra);
|
|
10516
|
+
}
|
|
10517
|
+
/**
|
|
10518
|
+
* `Table.Header`: indicador de orden en cada columna ordenable, clase sticky si se pidió y la
|
|
10519
|
+
* columna de selección delante si toca.
|
|
10520
|
+
*
|
|
10521
|
+
* Con columnas dinámicas (`columns` + función) la columna de selección no se puede colar en el
|
|
10522
|
+
* array de la app — su función de render la recibiría como si fuera una columna de negocio. Se usa
|
|
10523
|
+
* la forma que documenta react-aria para mezclar estáticas y dinámicas: la estática delante y las
|
|
10524
|
+
* de la app en un `Table.Collection` con sus `items`. El `Collection` exterior del header hereda
|
|
10525
|
+
* `dependencies` por contexto (`CollectionBuilder`), así que no se pierden.
|
|
10526
|
+
*/
|
|
10527
|
+
function transformHeader(header, options) {
|
|
10528
|
+
const { children, columns, className } = header.props;
|
|
10529
|
+
const props = options.sticky
|
|
10530
|
+
? { className: withClass(className, STICKY_HEADER_CLASS) }
|
|
10531
|
+
: {};
|
|
10532
|
+
if (typeof children === "function") {
|
|
10533
|
+
const render = children;
|
|
10534
|
+
const wrapped = (column) => withSortIndicator(render(column));
|
|
10535
|
+
if (!options.insertSelection) {
|
|
10536
|
+
return $8WNJc$react.cloneElement(header, { ...props, children: wrapped });
|
|
10537
|
+
}
|
|
10538
|
+
return $8WNJc$react.cloneElement(header, { ...props, columns: undefined }, jsxRuntime.jsx(SelectionColumn, {}, SELECTION_KEY), jsxRuntime.jsx(react.Table.Collection, { items: columns, children: wrapped }, "columns"));
|
|
10539
|
+
}
|
|
10540
|
+
const mapped = spread(mapNodes(children, withSortIndicator));
|
|
10541
|
+
if (!options.insertSelection) {
|
|
10542
|
+
return $8WNJc$react.cloneElement(header, props, ...mapped);
|
|
10543
|
+
}
|
|
10544
|
+
return $8WNJc$react.cloneElement(header, props, jsxRuntime.jsx(SelectionColumn, {}, SELECTION_KEY), ...mapped);
|
|
10545
|
+
}
|
|
10546
|
+
/**
|
|
10547
|
+
* Casilla delante de las celdas de una fila. Solo sabe tratar `Table.Row` (el mismo objeto que el
|
|
10548
|
+
* `TableRow` suelto): una fila envuelta en un componente propio de la app no se puede abrir, y se
|
|
10549
|
+
* devuelve tal cual (límite documentado en el README).
|
|
10550
|
+
*/
|
|
10551
|
+
function withSelectionCell(row) {
|
|
10552
|
+
if (!isElementOf(row, react.Table.Row)) {
|
|
10553
|
+
return row;
|
|
10554
|
+
}
|
|
10555
|
+
const { children, columns } = row.props;
|
|
10556
|
+
if (typeof children === "function") {
|
|
10557
|
+
// Mismo mecanismo que el header: estática + `Collection` con las columnas de la app. El
|
|
10558
|
+
// `idScope` de la fila (su `id`) lo hereda la colección interior por contexto, así que las
|
|
10559
|
+
// claves de las celdas siguen siendo únicas por fila.
|
|
10560
|
+
return $8WNJc$react.cloneElement(row, { columns: undefined }, jsxRuntime.jsx(SelectionCell, {}, SELECTION_KEY), jsxRuntime.jsx(react.Table.Collection, { items: columns, children: children }, "cells"));
|
|
10561
|
+
}
|
|
10562
|
+
if (hasSelectionControl(children)) {
|
|
10563
|
+
return row;
|
|
10564
|
+
}
|
|
10565
|
+
return $8WNJc$react.cloneElement(row, undefined, jsxRuntime.jsx(SelectionCell, {}, SELECTION_KEY), ...spread(children));
|
|
10566
|
+
}
|
|
10567
|
+
/** `Table.Body`: una casilla por fila, tanto con `items` + función como con filas estáticas. */
|
|
10568
|
+
function transformBody(body) {
|
|
10569
|
+
const { children } = body.props;
|
|
10570
|
+
if (typeof children === "function") {
|
|
10571
|
+
const render = children;
|
|
10572
|
+
return $8WNJc$react.cloneElement(body, {
|
|
10573
|
+
children: (item) => withSelectionCell(render(item)),
|
|
10574
|
+
});
|
|
10575
|
+
}
|
|
10576
|
+
return $8WNJc$react.cloneElement(body, undefined, ...spread(mapNodes(children, withSelectionCell)));
|
|
10577
|
+
}
|
|
10578
|
+
|
|
10579
|
+
/** Default de v2 (`use-table`, `@heroui/table@2.2.32`), sin reinterpretar. */
|
|
10580
|
+
function defaultShowCheckboxes(props) {
|
|
10581
|
+
const behavior = props.selectionBehavior ?? "toggle";
|
|
10582
|
+
return props.selectionMode === "multiple" && behavior !== "replace";
|
|
10583
|
+
}
|
|
10584
|
+
/** Primer `Table.Header` del árbol (atravesando arrays y Fragments), para decidir la selección. */
|
|
10585
|
+
function findHeader(node) {
|
|
10586
|
+
let found;
|
|
10587
|
+
mapNodes(node, (child) => {
|
|
10588
|
+
if (!found && isElementOf(child, react.Table.Header)) {
|
|
10589
|
+
found = child;
|
|
10590
|
+
}
|
|
10591
|
+
return child;
|
|
10592
|
+
});
|
|
10593
|
+
return found;
|
|
10594
|
+
}
|
|
10595
|
+
/**
|
|
10596
|
+
* `Table.Content` de Aurora: el `Table.Content` de HeroUI v3 con las casillas de selección, el
|
|
10597
|
+
* indicador de orden y la cabecera fija de v2. Idempotente: si la app ya montó sus casillas
|
|
10598
|
+
* (`slot="selection"`) o su `SortableColumnHeader`, no se inserta nada encima.
|
|
10599
|
+
*/
|
|
10600
|
+
function TableContent({ children, isHeaderSticky, showSelectionCheckboxes, ...props }) {
|
|
10601
|
+
const rootSticky = $8WNJc$react.useContext(TableStickyContext);
|
|
10602
|
+
const sticky = isHeaderSticky ?? rootSticky ?? false;
|
|
10603
|
+
const header = findHeader(children);
|
|
10604
|
+
// La decisión es de TABLA, no de fila: si la cabecera ya trae casilla (la montó la app), no
|
|
10605
|
+
// se inserta ni en la cabecera ni en las filas — insertar solo en unas descuadraría el número
|
|
10606
|
+
// de celdas y react-aria lanza `Cell count must match column count`.
|
|
10607
|
+
const insertSelection = (showSelectionCheckboxes ?? defaultShowCheckboxes(props)) &&
|
|
10608
|
+
header !== undefined &&
|
|
10609
|
+
!(typeof header.props.children !== "function" &&
|
|
10610
|
+
hasSelectionControl(header.props.children));
|
|
10611
|
+
const content = mapNodes(children, (child) => {
|
|
10612
|
+
if (isElementOf(child, react.Table.Header)) {
|
|
10613
|
+
return transformHeader(child, { insertSelection, sticky });
|
|
10614
|
+
}
|
|
10615
|
+
if (insertSelection && isElementOf(child, react.Table.Body)) {
|
|
10616
|
+
return transformBody(child);
|
|
10617
|
+
}
|
|
10618
|
+
return child;
|
|
10619
|
+
});
|
|
10620
|
+
// Esparcido y no `{content}`: ver `spread` (un array como hijo único exigiría claves).
|
|
10621
|
+
return $8WNJc$react.createElement(react.Table.Content, props, ...spread(content));
|
|
10622
|
+
}
|
|
10623
|
+
TableContent.displayName = "Aurora.Table.Content";
|
|
10624
|
+
|
|
10625
|
+
/**
|
|
10626
|
+
* Piezas que v3 monta FUERA del contenedor con scroll, como hermanas del root: el footer se queda
|
|
10627
|
+
* a ancho completo (comentario de los autores en `table.css:15-18`), no se desplaza con la tabla.
|
|
10628
|
+
*/
|
|
10629
|
+
const OUTSIDE_SCROLL = new Set([react.Table.Footer]);
|
|
10630
|
+
/**
|
|
10631
|
+
* Piezas que YA son un contenedor con scroll propio (`.table__scroll-container` /
|
|
10632
|
+
* `.table__resizable-container`, ambas con `overflow` en `table.css`): si el consumidor monta una,
|
|
10633
|
+
* la fachada no envuelve nada — anidar dos contenedores de scroll dejaría el interior sin ancho
|
|
10634
|
+
* que desbordar.
|
|
10635
|
+
*/
|
|
10636
|
+
const OWN_SCROLL = new Set([
|
|
10637
|
+
react.Table.ScrollContainer,
|
|
10638
|
+
react.Table.ResizableContainer,
|
|
10639
|
+
]);
|
|
10640
|
+
/** Identidad por `child.type` contra los objetos reales de HeroUI (mismo patrón que la fachada `Alert`). */
|
|
10641
|
+
function isPart$2(node, parts) {
|
|
10642
|
+
return $8WNJc$react.isValidElement(node) && parts.has(node.type);
|
|
10643
|
+
}
|
|
10644
|
+
/**
|
|
10645
|
+
* `isHeaderSticky` del root viaja a `Table.Content` por contexto (el `<thead>` lo pinta el
|
|
10646
|
+
* contenido). Solo se monta el provider cuando la app declara la prop: sin ella el árbol del root
|
|
10647
|
+
* queda exactamente como antes (`.Provider` y no `<Context value>` porque el peer admite React 18).
|
|
10648
|
+
*/
|
|
10649
|
+
function withSticky(isHeaderSticky, root) {
|
|
10650
|
+
return isHeaderSticky === undefined ? (root) : (jsxRuntime.jsx(TableStickyContext.Provider, { value: isHeaderSticky, children: root }));
|
|
10651
|
+
}
|
|
10652
|
+
const TableFacade = ({ children, isHeaderSticky, ...props }) => {
|
|
10653
|
+
const nodes = $8WNJc$react.Children.toArray(children);
|
|
10654
|
+
// Sin hijos, o con un contenedor de scroll ya montado a mano: el root tal cual.
|
|
10655
|
+
if (nodes.length === 0 || nodes.some((node) => isPart$2(node, OWN_SCROLL))) {
|
|
10656
|
+
return withSticky(isHeaderSticky, jsxRuntime.jsx(react.Table, { ...props, children: children }));
|
|
10657
|
+
}
|
|
10658
|
+
const scrollable = nodes.filter((node) => !isPart$2(node, OUTSIDE_SCROLL));
|
|
10659
|
+
const outsideScroll = nodes.filter((node) => isPart$2(node, OUTSIDE_SCROLL));
|
|
10660
|
+
return withSticky(isHeaderSticky, jsxRuntime.jsxs(react.Table, { ...props, children: [jsxRuntime.jsx(react.Table.ScrollContainer, { children: scrollable }), outsideScroll] }));
|
|
10661
|
+
};
|
|
10662
|
+
TableFacade.displayName = "Aurora.Table";
|
|
10663
|
+
/**
|
|
10664
|
+
* `Table` de Aurora: el root de HeroUI v3 con el `ScrollContainer` que faltaba, más los estáticos
|
|
10665
|
+
* del compound de v3 — los MISMOS objetos que exporta `@heroui/react` (así que
|
|
10666
|
+
* `Table.Header`/`Table.Column isRowHeader`… se comportan igual que antes), SALVO `Table.Content`,
|
|
10667
|
+
* que es la fachada de Aurora: inserta las casillas de selección múltiple, el indicador de orden
|
|
10668
|
+
* de `allowsSorting` y la cabecera fija de `isHeaderSticky` (ver `TableContent.tsx`).
|
|
10669
|
+
*
|
|
10670
|
+
* `Table.Root` queda deliberadamente crudo (el root pelado de v3, sin scroll): es la vía de escape
|
|
10671
|
+
* para quien quiera montar la anatomía entera a mano.
|
|
10672
|
+
*/
|
|
10673
|
+
const Table = Object.assign(TableFacade, {
|
|
10674
|
+
Body: react.Table.Body,
|
|
10675
|
+
Cell: react.Table.Cell,
|
|
10676
|
+
Collection: react.Table.Collection,
|
|
10677
|
+
Column: react.Table.Column,
|
|
10678
|
+
ColumnResizer: react.Table.ColumnResizer,
|
|
10679
|
+
Content: TableContent,
|
|
10680
|
+
Footer: react.Table.Footer,
|
|
10681
|
+
Header: react.Table.Header,
|
|
10682
|
+
LoadMore: react.Table.LoadMore,
|
|
10683
|
+
LoadMoreContent: react.Table.LoadMoreContent,
|
|
10684
|
+
ResizableContainer: react.Table.ResizableContainer,
|
|
10685
|
+
Root: react.Table.Root,
|
|
10686
|
+
Row: react.Table.Row,
|
|
10687
|
+
ScrollContainer: react.Table.ScrollContainer,
|
|
9401
10688
|
SortableColumnHeader: react.Table.SortableColumnHeader,
|
|
9402
10689
|
});
|
|
9403
10690
|
|
|
@@ -9458,6 +10745,225 @@ function AuraTable({ columns, items, renderCell, children, className, ...props }
|
|
|
9458
10745
|
return (jsxRuntime.jsx(Table, { className: `rounded-none ${className ?? ""}`.trim(), children: jsxRuntime.jsxs(Table.Content, { ...props, children: [jsxRuntime.jsx(Table.Header, { columns: columns, children: (column) => (jsxRuntime.jsx(Table.Column, { isRowHeader: column.isRowHeader || column.key === rowHeaderKey, children: column.label }, column.key)) }), jsxRuntime.jsx(Table.Body, { items: items, children: (item) => (jsxRuntime.jsx(Table.Row, { columns: columns, children: (column) => (jsxRuntime.jsx(Table.Cell, { children: renderCell ? renderCell(item, column.key) : children })) }, item.id)) })] }) }));
|
|
9459
10746
|
}
|
|
9460
10747
|
|
|
10748
|
+
// ── Autocrecimiento entre `minRows` y `maxRows` (reposición de v2) ─────────────────────────────
|
|
10749
|
+
//
|
|
10750
|
+
// v2 montaba `react-textarea-autosize@8.5.9` (`@heroui/input@2.4.22`, `dist/chunk-RIE4DNUQ.mjs`)
|
|
10751
|
+
// y aquí esa dependencia no existe, así que se reimplementa su algoritmo, leído en su compilado
|
|
10752
|
+
// (`dist/react-textarea-autosize.browser.esm.js`, `calculateNodeHeight` + `resizeTextarea`):
|
|
10753
|
+
//
|
|
10754
|
+
// 1. Se copia el estilo que decide el ajuste de línea (fuente, relleno, borde, ancho…) a un
|
|
10755
|
+
// `<textarea>` OCULTO compartido, se le pone el valor (o el placeholder, o "x") y se lee su
|
|
10756
|
+
// `scrollHeight`. Se mide en un clon y no en el nodo real porque encoger el real a 0 para
|
|
10757
|
+
// medirlo mueve el scroll de la página cuando el campo está cerca del borde inferior.
|
|
10758
|
+
// 2. El alto de UNA línea sale del mismo clon con el valor "x", no de una constante: así cuadra
|
|
10759
|
+
// con la fuente y el tamaño que el campo tenga de verdad.
|
|
10760
|
+
// 3. `alto = min(máx, max(mín, contenido))`, con mín/máx = filas × línea + relleno (+ borde en
|
|
10761
|
+
// `border-box`). Si `maxRows < minRows`, gana `maxRows` — el `min` va por fuera, como en v2.
|
|
10762
|
+
// 4. Se escribe con `setProperty("height", …, "important")`, literal de v2.
|
|
10763
|
+
//
|
|
10764
|
+
// Cuándo se recalcula (los mismos disparadores que v2, más el ancho):
|
|
10765
|
+
// · en cada render de la fachada (layout effect sin dependencias, como v2) → cubre el valor
|
|
10766
|
+
// CONTROLADO y cualquier cambio de `size`/`className` que mueva la fuente o el ancho;
|
|
10767
|
+
// · al escribir, en los dos modos (`resize` desde el `onChange` de la fachada). Hace falta
|
|
10768
|
+
// aunque el valor viva en estado de React: en modo NO controlado ese estado es del
|
|
10769
|
+
// `TextField` de react-aria, y su re-render no re-renderiza la fachada, así que el layout
|
|
10770
|
+
// effect de arriba no corre;
|
|
10771
|
+
// · al cambiar el ANCHO del campo (`ResizeObserver`; v2 solo escuchaba `window.resize`, que no
|
|
10772
|
+
// ve un panel que se estrecha) — solo si cambió el ancho, para no reaccionar a nuestro propio
|
|
10773
|
+
// cambio de alto;
|
|
10774
|
+
// · al terminar de cargar una fuente web (`document.fonts` → `loadingdone`, como v2);
|
|
10775
|
+
// · tras el `reset` del formulario (como v2): `useFormReset` de react-aria devuelve el estado del
|
|
10776
|
+
// `TextField` al `defaultValue`, otra vez sin re-renderizar la fachada. Se mide en el frame
|
|
10777
|
+
// siguiente, cuando React ya ha escrito el valor restaurado en el DOM.
|
|
10778
|
+
//
|
|
10779
|
+
// Scroll: v2 ocultaba la barra mientras no se alcanzaba el tope (`data-hide-scroll` →
|
|
10780
|
+
// `scrollbar-hide`, `@heroui/theme@2.4.15`); aquí se consigue lo mismo con `overflow-y: hidden`
|
|
10781
|
+
// inline hasta que el contenido supera el máximo, y a partir de ahí se deja el scroll nativo.
|
|
10782
|
+
const HIDDEN_TEXTAREA_STYLE = {
|
|
10783
|
+
"min-height": "0",
|
|
10784
|
+
"max-height": "none",
|
|
10785
|
+
height: "0",
|
|
10786
|
+
visibility: "hidden",
|
|
10787
|
+
overflow: "hidden",
|
|
10788
|
+
position: "absolute",
|
|
10789
|
+
"z-index": "-1000",
|
|
10790
|
+
top: "0",
|
|
10791
|
+
right: "0",
|
|
10792
|
+
display: "block",
|
|
10793
|
+
};
|
|
10794
|
+
// Lista literal de `react-textarea-autosize` (`SIZING_STYLE`).
|
|
10795
|
+
const SIZING_STYLE = [
|
|
10796
|
+
"borderBottomWidth",
|
|
10797
|
+
"borderLeftWidth",
|
|
10798
|
+
"borderRightWidth",
|
|
10799
|
+
"borderTopWidth",
|
|
10800
|
+
"boxSizing",
|
|
10801
|
+
"fontFamily",
|
|
10802
|
+
"fontSize",
|
|
10803
|
+
"fontStyle",
|
|
10804
|
+
"fontWeight",
|
|
10805
|
+
"letterSpacing",
|
|
10806
|
+
"lineHeight",
|
|
10807
|
+
"paddingBottom",
|
|
10808
|
+
"paddingLeft",
|
|
10809
|
+
"paddingRight",
|
|
10810
|
+
"paddingTop",
|
|
10811
|
+
"tabSize",
|
|
10812
|
+
"textIndent",
|
|
10813
|
+
"textRendering",
|
|
10814
|
+
"textTransform",
|
|
10815
|
+
"width",
|
|
10816
|
+
"wordBreak",
|
|
10817
|
+
"wordSpacing",
|
|
10818
|
+
"scrollbarGutter",
|
|
10819
|
+
];
|
|
10820
|
+
let hiddenTextarea = null;
|
|
10821
|
+
const forceHiddenStyles = (node) => {
|
|
10822
|
+
for (const key of Object.keys(HIDDEN_TEXTAREA_STYLE)) {
|
|
10823
|
+
node.style.setProperty(key, HIDDEN_TEXTAREA_STYLE[key], "important");
|
|
10824
|
+
}
|
|
10825
|
+
};
|
|
10826
|
+
const px = (value) => Number.parseFloat(value) || 0;
|
|
10827
|
+
/**
|
|
10828
|
+
* Mide el alto que necesita `node` para su contenido y lo acota entre `minRows` y `maxRows`.
|
|
10829
|
+
* Devuelve `null` si el nodo está fuera del DOM (no hay estilo computado que leer).
|
|
10830
|
+
*/
|
|
10831
|
+
function measureTextareaHeight(node, minRows, maxRows) {
|
|
10832
|
+
// v2 detectaba el nodo desconectado por un `boxSizing` vacío; aquí se pregunta directamente,
|
|
10833
|
+
// y un `boxSizing` vacío en un nodo conectado se trata como `content-box` (el valor inicial).
|
|
10834
|
+
if (!node.isConnected) {
|
|
10835
|
+
return null;
|
|
10836
|
+
}
|
|
10837
|
+
const computed = window.getComputedStyle(node);
|
|
10838
|
+
const isBorderBox = computed.boxSizing === "border-box";
|
|
10839
|
+
const paddingSize = px(computed.paddingTop) + px(computed.paddingBottom);
|
|
10840
|
+
const borderSize = px(computed.borderTopWidth) + px(computed.borderBottomWidth);
|
|
10841
|
+
if (!hiddenTextarea) {
|
|
10842
|
+
hiddenTextarea = document.createElement("textarea");
|
|
10843
|
+
hiddenTextarea.setAttribute("tabindex", "-1");
|
|
10844
|
+
hiddenTextarea.setAttribute("aria-hidden", "true");
|
|
10845
|
+
}
|
|
10846
|
+
if (hiddenTextarea.parentNode === null) {
|
|
10847
|
+
document.body.appendChild(hiddenTextarea);
|
|
10848
|
+
}
|
|
10849
|
+
const clone = hiddenTextarea;
|
|
10850
|
+
const cloneStyle = clone.style;
|
|
10851
|
+
for (const key of SIZING_STYLE) {
|
|
10852
|
+
cloneStyle[key] =
|
|
10853
|
+
computed[key] ?? "";
|
|
10854
|
+
}
|
|
10855
|
+
forceHiddenStyles(clone);
|
|
10856
|
+
const contentOf = () => isBorderBox
|
|
10857
|
+
? clone.scrollHeight + borderSize
|
|
10858
|
+
: clone.scrollHeight - paddingSize;
|
|
10859
|
+
const text = node.value || node.placeholder || "x";
|
|
10860
|
+
clone.value = text;
|
|
10861
|
+
let content = contentOf();
|
|
10862
|
+
// Doble asignación y medida: bug de Firefox que v2 también esquiva (bugzilla 1795904).
|
|
10863
|
+
clone.value = text;
|
|
10864
|
+
content = contentOf();
|
|
10865
|
+
clone.value = "x";
|
|
10866
|
+
const rowHeight = clone.scrollHeight - paddingSize;
|
|
10867
|
+
const extra = isBorderBox ? paddingSize + borderSize : 0;
|
|
10868
|
+
const minHeight = rowHeight * minRows + extra;
|
|
10869
|
+
const maxHeight = rowHeight * maxRows + extra;
|
|
10870
|
+
return {
|
|
10871
|
+
height: Math.min(maxHeight, Math.max(minHeight, content)),
|
|
10872
|
+
rowHeight,
|
|
10873
|
+
overflows: content > maxHeight,
|
|
10874
|
+
};
|
|
10875
|
+
}
|
|
10876
|
+
// `useLayoutEffect` en el servidor avisa en React 18; el alto solo tiene sentido en el cliente.
|
|
10877
|
+
const useIsomorphicLayoutEffect = typeof window !== "undefined" ? $8WNJc$react.useLayoutEffect : $8WNJc$react.useEffect;
|
|
10878
|
+
/**
|
|
10879
|
+
* Autocrecimiento del `<textarea>` apuntado por `nodeRef`. Devuelve el `resize` para llamarlo
|
|
10880
|
+
* desde el `onChange` de la fachada.
|
|
10881
|
+
*/
|
|
10882
|
+
function useTextareaAutosize(nodeRef, { enabled, minRows, maxRows }) {
|
|
10883
|
+
const heightRef = $8WNJc$react.useRef(0);
|
|
10884
|
+
const overflowRef = $8WNJc$react.useRef(null);
|
|
10885
|
+
const resize = $8WNJc$react.useCallback(() => {
|
|
10886
|
+
const node = nodeRef.current;
|
|
10887
|
+
if (!(enabled && node)) {
|
|
10888
|
+
return;
|
|
10889
|
+
}
|
|
10890
|
+
const result = measureTextareaHeight(node, minRows, maxRows);
|
|
10891
|
+
if (!result) {
|
|
10892
|
+
return;
|
|
10893
|
+
}
|
|
10894
|
+
if (heightRef.current !== result.height) {
|
|
10895
|
+
heightRef.current = result.height;
|
|
10896
|
+
node.style.setProperty("height", `${result.height}px`, "important");
|
|
10897
|
+
}
|
|
10898
|
+
if (overflowRef.current !== result.overflows) {
|
|
10899
|
+
overflowRef.current = result.overflows;
|
|
10900
|
+
node.style.setProperty("overflow-y", result.overflows ? "auto" : "hidden");
|
|
10901
|
+
}
|
|
10902
|
+
}, [enabled, minRows, maxRows, nodeRef]);
|
|
10903
|
+
// La función más reciente para los listeners, que se registran una sola vez.
|
|
10904
|
+
const resizeRef = $8WNJc$react.useRef(resize);
|
|
10905
|
+
resizeRef.current = resize;
|
|
10906
|
+
// Cada render, como v2: el valor controlado, el `size` o la clase pueden haber cambiado.
|
|
10907
|
+
useIsomorphicLayoutEffect(() => {
|
|
10908
|
+
resize();
|
|
10909
|
+
});
|
|
10910
|
+
// Al apagar el autosize, se retira lo que se escribió a mano para que mande el CSS.
|
|
10911
|
+
useIsomorphicLayoutEffect(() => {
|
|
10912
|
+
const node = nodeRef.current;
|
|
10913
|
+
if (enabled || !node) {
|
|
10914
|
+
return;
|
|
10915
|
+
}
|
|
10916
|
+
node.style.removeProperty("height");
|
|
10917
|
+
node.style.removeProperty("overflow-y");
|
|
10918
|
+
heightRef.current = 0;
|
|
10919
|
+
overflowRef.current = null;
|
|
10920
|
+
}, [enabled, nodeRef]);
|
|
10921
|
+
$8WNJc$react.useEffect(() => {
|
|
10922
|
+
const node = nodeRef.current;
|
|
10923
|
+
if (!(enabled && node)) {
|
|
10924
|
+
return;
|
|
10925
|
+
}
|
|
10926
|
+
const cleanups = [];
|
|
10927
|
+
// Ancho: re-ajuste de línea. Solo cuando cambia el ANCHO, no el alto que ponemos nosotros.
|
|
10928
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
10929
|
+
let lastWidth = node.getBoundingClientRect().width;
|
|
10930
|
+
const observer = new ResizeObserver((entries) => {
|
|
10931
|
+
const width = entries[0]?.contentRect.width ?? lastWidth;
|
|
10932
|
+
if (width !== lastWidth) {
|
|
10933
|
+
lastWidth = width;
|
|
10934
|
+
resizeRef.current();
|
|
10935
|
+
}
|
|
10936
|
+
});
|
|
10937
|
+
observer.observe(node);
|
|
10938
|
+
cleanups.push(() => observer.disconnect());
|
|
10939
|
+
}
|
|
10940
|
+
else {
|
|
10941
|
+
const onWindowResize = () => resizeRef.current();
|
|
10942
|
+
window.addEventListener("resize", onWindowResize);
|
|
10943
|
+
cleanups.push(() => window.removeEventListener("resize", onWindowResize));
|
|
10944
|
+
}
|
|
10945
|
+
const fonts = document.fonts;
|
|
10946
|
+
if (fonts && typeof fonts.addEventListener === "function") {
|
|
10947
|
+
const onFonts = () => resizeRef.current();
|
|
10948
|
+
fonts.addEventListener("loadingdone", onFonts);
|
|
10949
|
+
cleanups.push(() => fonts.removeEventListener("loadingdone", onFonts));
|
|
10950
|
+
}
|
|
10951
|
+
const onReset = (event) => {
|
|
10952
|
+
if (node.form && node.form === event.target) {
|
|
10953
|
+
requestAnimationFrame(() => resizeRef.current());
|
|
10954
|
+
}
|
|
10955
|
+
};
|
|
10956
|
+
document.body.addEventListener("reset", onReset);
|
|
10957
|
+
cleanups.push(() => document.body.removeEventListener("reset", onReset));
|
|
10958
|
+
return () => {
|
|
10959
|
+
for (const cleanup of cleanups) {
|
|
10960
|
+
cleanup();
|
|
10961
|
+
}
|
|
10962
|
+
};
|
|
10963
|
+
}, [enabled, nodeRef]);
|
|
10964
|
+
return resize;
|
|
10965
|
+
}
|
|
10966
|
+
|
|
9461
10967
|
// TECH-2459 · F3 · G1-forms — v3 divide el `Textarea` monolítico de v2 en `TextField` +
|
|
9462
10968
|
// `TextArea` (el `<textarea>` real, sin `InputGroup` intermedio: a diferencia de Input, aquí el
|
|
9463
10969
|
// borde/fondo/tamaño de texto viven todos en el MISMO elemento — ver `textarea.css`). Por eso no
|
|
@@ -9496,6 +11002,8 @@ const FIELD_BORDER_Y = 4;
|
|
|
9496
11002
|
// El default de v2, literal: `({ minRows = 3, maxRows = 8, … })` en `@heroui/input@2.4.22`,
|
|
9497
11003
|
// `dist/chunk-RIE4DNUQ.mjs`.
|
|
9498
11004
|
const DEFAULT_MIN_ROWS = 3;
|
|
11005
|
+
// Mismo literal de v2: `maxRows = 8`. Tope del autocrecimiento cuando el consumidor no lo pasa.
|
|
11006
|
+
const DEFAULT_MAX_ROWS = 8;
|
|
9499
11007
|
// ── `minRows` gana al atributo nativo `rows` ─────────────────────────────────────────────────
|
|
9500
11008
|
//
|
|
9501
11009
|
// Las dos props dicen lo mismo —cuántas líneas de alto tiene la caja al aparecer— así que la
|
|
@@ -9533,7 +11041,7 @@ Math.max(1, Math.round(minRows ?? rows ?? DEFAULT_MIN_ROWS));
|
|
|
9533
11041
|
// esquina. Es el único resto que quedaba entre `textarea--default` y su referencia: 12 píxeles en
|
|
9534
11042
|
// x[780..786] y[407..413], gris `102,102,102` donde v2 tiene el fondo.
|
|
9535
11043
|
const RESIZE_CLASS = "resize-none";
|
|
9536
|
-
const RADIUS_CLASS$
|
|
11044
|
+
const RADIUS_CLASS$2 = {
|
|
9537
11045
|
none: "rounded-none",
|
|
9538
11046
|
sm: "rounded-[8px]",
|
|
9539
11047
|
md: "rounded-[12px]",
|
|
@@ -9651,7 +11159,12 @@ const INVALID_TEXT_CLASS = "text-danger";
|
|
|
9651
11159
|
const Textarea = $8WNJc$react.forwardRef(({ label, description, errorMessage, id, size = "md", variant = "bordered", radius = "md",
|
|
9652
11160
|
// Sin default en el destructuring a propósito: hace falta distinguir "no me la han pasado"
|
|
9653
11161
|
// de un valor, para que `rows` solo se mire cuando `minRows` no viene (ver `resolveRows`).
|
|
9654
|
-
minRows, rows,
|
|
11162
|
+
minRows, rows,
|
|
11163
|
+
// Tope del autocrecimiento, default de v2 (8). Se desestructura también para que no
|
|
11164
|
+
// llegue al DOM (antes viajaba en `...rest` y React avisaba de un atributo desconocido).
|
|
11165
|
+
maxRows = DEFAULT_MAX_ROWS,
|
|
11166
|
+
// Compat v2: `true` apaga el autocrecimiento y deja la caja fija en su alto mínimo.
|
|
11167
|
+
disableAutosize = false, placeholder, isInvalid = false, isRequired = false, isDisabled = false, isReadOnly = false,
|
|
9655
11168
|
// `fullWidth` por defecto a `true`: HeroUI v2 lo traía en sus `defaultVariants`
|
|
9656
11169
|
// (verificado en el paquete v2 compilado, `dist/components/input.js`:
|
|
9657
11170
|
// `defaultVariants: { variant:"flat", color:"default", size:"md", fullWidth:true, … }`, con
|
|
@@ -9662,7 +11175,32 @@ minRows, rows, placeholder, isInvalid = false, isRequired = false, isDisabled =
|
|
|
9662
11175
|
fullWidth = true, value, defaultValue, onChange, onValueChange, className, classNames, name, style, ...rest }, ref) => {
|
|
9663
11176
|
const generatedId = $8WNJc$react.useId();
|
|
9664
11177
|
const textareaId = id || generatedId;
|
|
9665
|
-
const
|
|
11178
|
+
const requestedRows = resolveRows(minRows, rows);
|
|
11179
|
+
const maxFieldRows = Math.max(1, Math.round(maxRows));
|
|
11180
|
+
// Con autocrecimiento, `maxRows` es el techo también del mínimo: v2 acotaba
|
|
11181
|
+
// `min(máx, max(mín, contenido))`, así que con `maxRows < minRows` ganaba `maxRows`. Sin
|
|
11182
|
+
// autocrecimiento `maxRows` no pinta nada (v2 ni se lo pasaba al `<textarea>` plano).
|
|
11183
|
+
const fieldRows = disableAutosize
|
|
11184
|
+
? requestedRows
|
|
11185
|
+
: Math.min(requestedRows, maxFieldRows);
|
|
11186
|
+
const innerRef = $8WNJc$react.useRef(null);
|
|
11187
|
+
const setRefs = $8WNJc$react.useCallback((node) => {
|
|
11188
|
+
innerRef.current = node;
|
|
11189
|
+
if (typeof ref === "function") {
|
|
11190
|
+
ref(node);
|
|
11191
|
+
}
|
|
11192
|
+
else if (ref) {
|
|
11193
|
+
ref.current = node;
|
|
11194
|
+
}
|
|
11195
|
+
}, [ref]);
|
|
11196
|
+
const resize = useTextareaAutosize(innerRef, {
|
|
11197
|
+
enabled: !disableAutosize,
|
|
11198
|
+
minRows: fieldRows,
|
|
11199
|
+
maxRows: maxFieldRows,
|
|
11200
|
+
});
|
|
11201
|
+
// El `min-height` se emite aunque haya autocrecimiento: es el alto correcto ANTES de la
|
|
11202
|
+
// primera medida (SSR, o sin hoja de estilos) y el suelo si la medida sale menor (p. ej.
|
|
11203
|
+
// `flat`, sin los 4px de borde). El `height` lo escribe después `useTextareaAutosize`.
|
|
9666
11204
|
// Inline y no una clase: el valor es un número arbitrario y Tailwind 4 solo emite las clases
|
|
9667
11205
|
// que encuentra escritas en el fuente, así que `min-h-[${n}px]` no existiría en el CSS. El
|
|
9668
11206
|
// `style` del consumidor va DESPUÉS para que pueda pisarlo (y una clase con `!` también gana,
|
|
@@ -9671,14 +11209,32 @@ fullWidth = true, value, defaultValue, onChange, onValueChange, className, class
|
|
|
9671
11209
|
minHeight: `${fieldRows * SIZE_LINE_HEIGHT[size] + FIELD_PADDING_Y + FIELD_BORDER_Y}px`,
|
|
9672
11210
|
...style,
|
|
9673
11211
|
};
|
|
11212
|
+
// El estado del valor lo actualiza el `TextField` con su propio `onChange` (llega al
|
|
11213
|
+
// `<textarea>` por contexto y `mergeProps` encadena los dos). Aquí: medir —el nodo ya trae
|
|
11214
|
+
// el valor nuevo, y en modo no controlado la fachada no se re-renderiza— y avisar.
|
|
9674
11215
|
const handleChange = (event) => {
|
|
11216
|
+
resize();
|
|
9675
11217
|
onChange?.(event);
|
|
9676
11218
|
onValueChange?.(event.target.value);
|
|
9677
11219
|
};
|
|
9678
|
-
return (
|
|
11220
|
+
return (
|
|
11221
|
+
// ── `value` / `defaultValue` van al `TextField`, NO al `<textarea>` ─────────────────────
|
|
11222
|
+
//
|
|
11223
|
+
// En react-aria el dueño del valor es el `TextField`: `useTextField` guarda el estado con
|
|
11224
|
+
// `useControlledState(value, defaultValue ?? "")` y se lo inyecta al `<textarea>` como
|
|
11225
|
+
// `value` por contexto (`react-aria-components/dist/private/TextField.mjs`). Antes la
|
|
11226
|
+
// fachada se los pasaba al `<textarea>`, así que:
|
|
11227
|
+
// · en modo NO controlado el `TextField` arrancaba con "" y el `<textarea>` recibía a la
|
|
11228
|
+
// vez `value=""` (del contexto) y `defaultValue` → React avisaba de «both value and
|
|
11229
|
+
// defaultValue» y el primer re-render del padre dejaba el campo VACÍO;
|
|
11230
|
+
// · en modo controlado funcionaba de rebote (`mergeProps` deja ganar al `value` propio),
|
|
11231
|
+
// pero el estado interno del `TextField` seguía en "".
|
|
11232
|
+
// Ahora el `TextField` tiene el valor en los dos modos, y el `reset` del formulario también
|
|
11233
|
+
// es suyo (`useFormReset` devuelve el estado al `defaultValue`; el autosize lo escucha).
|
|
11234
|
+
jsxRuntime.jsxs(react.TextField, { id: textareaId, value: value, defaultValue: defaultValue, name: name, isInvalid: isInvalid, isRequired: isRequired, isDisabled: isDisabled, isReadOnly: isReadOnly, className: react.cn(FIELD_STACK_CLASS, fullWidth ? "w-full" : "",
|
|
9679
11235
|
// El `className` del consumidor, en la raíz y en último lugar: el slot `base` de
|
|
9680
11236
|
// 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:
|
|
11237
|
+
className), children: [label && (jsxRuntime.jsx(react.Label, { className: react.cn(LABEL_CLASS, isInvalid ? INVALID_LABEL_CLASS : "", classNames?.label), children: label })), jsxRuntime.jsx(react.TextArea, { ...rest, ref: setRefs, rows: fieldRows, style: fieldStyle, placeholder: placeholder, onChange: handleChange, className: react.cn(SIZE_TEXT_CLASS[size], RESIZE_CLASS, RADIUS_CLASS$2[radius], VARIANT_CLASS[variant], isInvalid ? INVALID_TEXT_CLASS : "", FIELD_FOCUS_CLASS, FIELD_SHADOW_CLASS,
|
|
9682
11238
|
// Incondicional, como el slot `input` de v2: el campo llena siempre su raíz,
|
|
9683
11239
|
// y es la raíz la que responde a `fullWidth` y al ancho del consumidor.
|
|
9684
11240
|
"w-full", classNames?.input ?? classNames?.inputWrapper) }), description && !isInvalid && (jsxRuntime.jsx(react.Description, { className: react.cn(HELPER_CLASS, "text-left", classNames?.description), children: description })), isInvalid && errorMessage && (jsxRuntime.jsx(react.FieldError, { className: react.cn(HELPER_CLASS, "text-left", classNames?.errorMessage), children: errorMessage }))] }));
|
|
@@ -13940,10 +15496,12 @@ const createCurrencyFormatter = (locale, currency, fractionDigits = 0) => {
|
|
|
13940
15496
|
return (val) => formatter.format(val);
|
|
13941
15497
|
};
|
|
13942
15498
|
|
|
15499
|
+
const ModalFacadeContext = $8WNJc$react.createContext({});
|
|
15500
|
+
|
|
13943
15501
|
// v2 no tiene prop `radius` en el diálogo del modal: v3 tampoco (eliminada, "usar Tailwind"), así
|
|
13944
15502
|
// que se reimplementa vía className arbitrario — mismo criterio y mismos valores exactos que
|
|
13945
15503
|
// Button/Card de F3.0 (RADIUS_CLASS).
|
|
13946
|
-
const RADIUS_CLASS$
|
|
15504
|
+
const RADIUS_CLASS$1 = {
|
|
13947
15505
|
none: "rounded-none",
|
|
13948
15506
|
sm: "rounded-[8px]",
|
|
13949
15507
|
md: "rounded-[12px]",
|
|
@@ -14001,6 +15559,49 @@ const MAX_WIDTH_CLASS = {
|
|
|
14001
15559
|
* `ModalContent` tenga que ser hijo directo de `Modal.Dialog`.
|
|
14002
15560
|
*/
|
|
14003
15561
|
const ModalCloseContext = $8WNJc$react.createContext(() => { });
|
|
15562
|
+
/**
|
|
15563
|
+
* Atributos que el `Dialog` de react-aria SÍ deja pasar a su `<section>`: `id`, `role` (vía
|
|
15564
|
+
* `useDialog`), `data-*` y los cuatro "labelable" (`filterDOMProps(props, { labelable: true })`
|
|
15565
|
+
* en `react-aria/dist/private/utils/filterDOMProps.mjs`). Estos van como props para que
|
|
15566
|
+
* react-aria los vea — sobre todo `aria-label`/`aria-labelledby`, que deciden si avisa de un
|
|
15567
|
+
* diálogo sin nombre.
|
|
15568
|
+
*/
|
|
15569
|
+
const LABELABLE_PROPS = new Set([
|
|
15570
|
+
"aria-label",
|
|
15571
|
+
"aria-labelledby",
|
|
15572
|
+
"aria-describedby",
|
|
15573
|
+
"aria-details",
|
|
15574
|
+
]);
|
|
15575
|
+
/**
|
|
15576
|
+
* Reparte el resto de props de `Modal` entre las dos vías de llegada al diálogo. En v2 TODAS
|
|
15577
|
+
* llegaban (`mergeProps(modalProps, otherProps, …)` sobre el `<section>`, sin filtrar); en v3 el
|
|
15578
|
+
* `Dialog` filtra, así que un `aria-busy` se perdería en silencio si solo fuera como prop. Lo que
|
|
15579
|
+
* no es `aria-*`/`data-*`/`id`/`role` se descarta: `ModalProps` no lo declara y v2 lo habría
|
|
15580
|
+
* volcado al DOM con el aviso de React de atributo desconocido.
|
|
15581
|
+
*/
|
|
15582
|
+
const splitDialogDOMProps = (rest) => {
|
|
15583
|
+
const dialogProps = {};
|
|
15584
|
+
const extraAria = {};
|
|
15585
|
+
for (const [key, value] of Object.entries(rest)) {
|
|
15586
|
+
// Un `aria-labelledby={undefined}` explícito no debe borrar el nombre por defecto.
|
|
15587
|
+
if (value === undefined) {
|
|
15588
|
+
continue;
|
|
15589
|
+
}
|
|
15590
|
+
if (key === "id" ||
|
|
15591
|
+
key === "role" ||
|
|
15592
|
+
key.startsWith("data-") ||
|
|
15593
|
+
LABELABLE_PROPS.has(key)) {
|
|
15594
|
+
dialogProps[key] = value;
|
|
15595
|
+
}
|
|
15596
|
+
else if (key.startsWith("aria-")) {
|
|
15597
|
+
extraAria[key] = value;
|
|
15598
|
+
}
|
|
15599
|
+
}
|
|
15600
|
+
return {
|
|
15601
|
+
dialogProps: dialogProps,
|
|
15602
|
+
extraAria: extraAria,
|
|
15603
|
+
};
|
|
15604
|
+
};
|
|
14004
15605
|
/**
|
|
14005
15606
|
* Modal - fachada BeweOS sobre el `Modal` compound de HeroUI v3.
|
|
14006
15607
|
*
|
|
@@ -14008,15 +15609,16 @@ const ModalCloseContext = $8WNJc$react.createContext(() => { });
|
|
|
14008
15609
|
* Dialog/Header/Body/Footer/CloseTrigger`, ver `upload-file/UploadFile.tsx` D-G1-7 y la guía
|
|
14009
15610
|
* oficial `modal`). Esta fachada arma esa anatomía por dentro pero conserva la API PLANA de v2
|
|
14010
15611
|
* (`isOpen`/`onOpenChange`/`onClose`/`size`/`radius`/`backdrop`/`scrollBehavior`/`placement`/
|
|
14011
|
-
* `isDismissable`/`hideCloseButton` + `<ModalContent>{(onClose) => ...}</ModalContent>`),
|
|
14012
|
-
*
|
|
15612
|
+
* `isDismissable`/`hideCloseButton` + `<ModalContent>{(onClose) => ...}</ModalContent>`), más
|
|
15613
|
+
* `classNames` por slot y `aria-*`/`data-*`/`id`/`role` al diálogo, para que el código ya escrito
|
|
15614
|
+
* en las 4 apps consumidoras siga funcionando sin cambios.
|
|
14013
15615
|
*
|
|
14014
15616
|
* Accesibilidad (criterio de aceptación de la issue, verificado con Playwright): el foco entra al
|
|
14015
15617
|
* abrir, queda atrapado dentro del diálogo, Escape cierra (salvo `isKeyboardDismissDisabled`), el
|
|
14016
15618
|
* foco vuelve al disparador al cerrar y la página no hace scroll por detrás — los cuatro los
|
|
14017
15619
|
* gestiona react-aria-components internamente (`ModalOverlay`/`Dialog`), sin código propio.
|
|
14018
15620
|
*/
|
|
14019
|
-
const Modal = ({ isOpen, defaultOpen, onOpenChange, onClose, size = "md", radius = "lg", backdrop = "opaque", scrollBehavior = "normal", placement, isDismissable = true, isKeyboardDismissDisabled = false, hideCloseButton = false, className, children, }) => {
|
|
15621
|
+
const Modal = ({ isOpen, defaultOpen, onOpenChange, onClose, size = "md", radius = "lg", backdrop = "opaque", scrollBehavior = "normal", placement, isDismissable = true, isKeyboardDismissDisabled = false, hideCloseButton = false, className, classNames, children, ...rest }) => {
|
|
14020
15622
|
// v2 aceptaba `onClose` además de `onOpenChange` y lo llamaba en cada cierre; v3 solo conoce
|
|
14021
15623
|
// `onOpenChange`. Sin este puente, un modal controlado con `isOpen` + `onClose` (el patrón
|
|
14022
15624
|
// v2 dominante en las apps: ~118 usos en beweossmbs frente a 8 de `onOpenChange`) no se
|
|
@@ -14034,23 +15636,93 @@ const Modal = ({ isOpen, defaultOpen, onOpenChange, onClose, size = "md", radius
|
|
|
14034
15636
|
onClose?.();
|
|
14035
15637
|
}
|
|
14036
15638
|
};
|
|
14037
|
-
|
|
15639
|
+
// v2 nombraba el diálogo con su cabecera (`aria-labelledby` = id del `ModalHeader`,
|
|
15640
|
+
// `getDialogProps` de `@heroui/modal@2.2.19`); en v3 solo lo hace `Modal.Heading`, que las apps
|
|
15641
|
+
// no usan. Mismo arreglo que `Drawer`: `ModalHeader` pone este id en su nodo.
|
|
15642
|
+
//
|
|
15643
|
+
// Si el consumidor trae su propio `aria-label`/`aria-labelledby` (3 `<Modal>` de beweossmbs,
|
|
15644
|
+
// p. ej. `create-note-modal`), manda el suyo. Aquí v3 mejora a v2: v2 escribía
|
|
15645
|
+
// `aria-labelledby` DESPUÉS del spread, así que pisaba siempre el del consumidor.
|
|
15646
|
+
const headerId = $8WNJc$react.useId();
|
|
15647
|
+
// Props de `ModalContent` (registradas desde dentro del diálogo, ver `ModalContent.tsx`). En
|
|
15648
|
+
// v2 `ModalContent` era el diálogo: `getDialogProps(mergeProps(dialogProps, otherProps))`
|
|
15649
|
+
// (`@heroui/modal@2.2.19`, `chunk-UKDXCIYN.mjs`), y dentro de `getDialogProps`
|
|
15650
|
+
// (`chunk-Z2LRST4N.mjs`):
|
|
15651
|
+
// - atributos: `mergeProps(modalProps, <resto de Modal>, <props de ModalContent>)` →
|
|
15652
|
+
// ModalContent gana a Modal;
|
|
15653
|
+
// - clase: `slots.base({ class: clsx(clsx(classNames.base, className), content.className) })`
|
|
15654
|
+
// → fachada (tv) < `classNames.base` < `className` de Modal < `className` de ModalContent,
|
|
15655
|
+
// con `twMerge`. Es el orden del `cn` de abajo.
|
|
15656
|
+
const [content, setContent] = $8WNJc$react.useState({});
|
|
15657
|
+
const registerContentProps = $8WNJc$react.useCallback((props) => setContent(props ?? {}), []);
|
|
15658
|
+
const { className: contentClassName, ...contentRest } = content;
|
|
15659
|
+
const { dialogProps, extraAria } = splitDialogDOMProps({
|
|
15660
|
+
...rest,
|
|
15661
|
+
...contentRest,
|
|
15662
|
+
});
|
|
15663
|
+
const hasOwnName = dialogProps["aria-label"] !== undefined ||
|
|
15664
|
+
dialogProps["aria-labelledby"] !== undefined;
|
|
15665
|
+
const hasExtraAria = Object.keys(extraAria).length > 0;
|
|
15666
|
+
// Sin `Modal.Root` (el `DialogTrigger` de react-aria) a propósito, igual que `Drawer`: la
|
|
15667
|
+
// fachada no tiene disparador propio —la abre el `isOpen` de la app— y un `DialogTrigger`
|
|
15668
|
+
// sin hijo pulsable avisaba en consola en CADA montaje («A PressResponder was rendered
|
|
15669
|
+
// without a pressable child», medido en jsdom: 9 avisos en `Modal.onClose.test.tsx`).
|
|
15670
|
+
// `Modal.Backdrop` (`ModalOverlay`) ya crea y publica su propio estado de overlay con
|
|
15671
|
+
// `isOpen`/`onOpenChange` —el que usan la X y el `close` del render-prop— y calcula todos los
|
|
15672
|
+
// slots de `modalVariants()` aunque no haya root encima; el root solo aportaba ese aviso y un
|
|
15673
|
+
// `aria-labelledby` de reserva que apuntaba al id de un disparador que no existe.
|
|
15674
|
+
return (jsxRuntime.jsx(react.Modal.Backdrop, { isOpen: isOpen, defaultOpen: defaultOpen, onOpenChange: handleOpenChange, variant: backdrop, isDismissable: isDismissable, isKeyboardDismissDisabled: isKeyboardDismissDisabled,
|
|
15675
|
+
// v2 `classNames.backdrop` → la capa que oscurece la página. Utilidad en
|
|
15676
|
+
// `@layer utilities`, gana a `.modal__backdrop--opaque` (`@layer components`).
|
|
15677
|
+
className: classNames?.backdrop, children: jsxRuntime.jsx(react.Modal.Container, { size: SIZE_MAP[size], placement: placement, scroll: scrollBehavior === "inside" ? "inside" : undefined,
|
|
15678
|
+
// v2 `classNames.wrapper` (el `div` que posiciona el diálogo, `data-slot="wrapper"`)
|
|
15679
|
+
// es el `Modal.Container` de v3: su `p-4 sm:p-10` es lo que pisa un `wrapper="p-0"`.
|
|
15680
|
+
className: classNames?.wrapper, children: jsxRuntime.jsx(react.Modal.Dialog, { ...(hasOwnName ? {} : { "aria-labelledby": headerId }), ...dialogProps,
|
|
15681
|
+
// Los `aria-*` que react-aria filtra (`aria-busy`…) entran por el `render` del
|
|
15682
|
+
// `<section>`: van ANTES de sus props, así que `role`/`aria-modal`/`tabIndex`
|
|
15683
|
+
// de react-aria no se pueden pisar por esta vía.
|
|
15684
|
+
render: hasExtraAria
|
|
15685
|
+
? (sectionProps) => jsxRuntime.jsx("section", { ...extraAria, ...sectionProps })
|
|
15686
|
+
: undefined, className: react.cn(RADIUS_CLASS$1[radius], size !== "full" && MAX_WIDTH_CLASS[size], "px-6 py-9", classNames?.base, className, contentClassName), children: ({ close }) => (jsxRuntime.jsx(ModalDialogBody, { close: close, headerId: headerId, classNames: classNames, registerContentProps: registerContentProps, hideCloseButton: hideCloseButton, children: children })) }) }) }));
|
|
15687
|
+
};
|
|
15688
|
+
/** Aísla el `useMemo` del valor de contexto (el render-prop de `Dialog` no puede usar hooks). */
|
|
15689
|
+
const ModalDialogBody = ({ close, headerId, classNames, registerContentProps, hideCloseButton, children, }) => {
|
|
15690
|
+
const value = $8WNJc$react.useMemo(() => ({ headerId, classNames, registerContentProps }), [headerId, classNames, registerContentProps]);
|
|
15691
|
+
return (jsxRuntime.jsx(ModalCloseContext.Provider, { value: close, children: jsxRuntime.jsxs(ModalFacadeContext.Provider, { value: value, children: [!hideCloseButton && (jsxRuntime.jsx(react.Modal.CloseTrigger, { className: classNames?.closeButton })), children] }) }));
|
|
14038
15692
|
};
|
|
14039
15693
|
|
|
15694
|
+
/**
|
|
15695
|
+
* Clave estable de las props registradas: solo primitivos (`className`, `aria-*`, `data-*`…), así
|
|
15696
|
+
* que basta con serializarlas. Evita re-registrar —y re-renderizar el `Modal`— en cada render.
|
|
15697
|
+
*/
|
|
15698
|
+
const keyOf = (props) => JSON.stringify(Object.entries(props)
|
|
15699
|
+
.filter(([, value]) => value !== undefined)
|
|
15700
|
+
.sort(([a], [b]) => (a < b ? -1 : 1)));
|
|
14040
15701
|
/**
|
|
14041
15702
|
* `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
|
|
14043
|
-
* compatibilidad:
|
|
14044
|
-
* `
|
|
14045
|
-
*
|
|
15703
|
+
* / D-F2-3). Este componente NO es un wrapper de ningún compound v3 — es azúcar de
|
|
15704
|
+
* compatibilidad: sus `children` ya se renderizan dentro de `Modal.Dialog` (ver `Modal.tsx`) y, si
|
|
15705
|
+
* `children` es una función, la invoca con el `close` real del diálogo (vía contexto) para que el
|
|
15706
|
+
* patrón v2 `{(onClose) => <>...</>}` siga funcionando tal cual.
|
|
15707
|
+
*
|
|
15708
|
+
* Sus demás props (`className`, `aria-busy`, `data-*`…) NO se pintan aquí: en v2 `ModalContent`
|
|
15709
|
+
* era el propio diálogo, así que se registran en el `Modal` y este las aplica al `role="dialog"`
|
|
15710
|
+
* (antes caían en un `<div>` intermedio y, p. ej., el `h-[95vh] p-0 rounded-2xl` de
|
|
15711
|
+
* `chatbot-config` no llegaba al diálogo). Sin props no se registra nada: el render es el mismo.
|
|
14046
15712
|
*/
|
|
14047
|
-
function ModalContent({ children,
|
|
15713
|
+
function ModalContent({ children, ...dialogProps }) {
|
|
14048
15714
|
const close = $8WNJc$react.useContext(ModalCloseContext);
|
|
14049
|
-
const
|
|
14050
|
-
|
|
14051
|
-
|
|
14052
|
-
|
|
14053
|
-
|
|
15715
|
+
const { registerContentProps } = $8WNJc$react.useContext(ModalFacadeContext);
|
|
15716
|
+
const key = keyOf(dialogProps);
|
|
15717
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: `key` resume `dialogProps` (primitivos); depender del objeto re-registraría en cada render.
|
|
15718
|
+
$8WNJc$react.useLayoutEffect(() => {
|
|
15719
|
+
if (!registerContentProps || key === "[]") {
|
|
15720
|
+
return;
|
|
15721
|
+
}
|
|
15722
|
+
registerContentProps(dialogProps);
|
|
15723
|
+
return () => registerContentProps(undefined);
|
|
15724
|
+
}, [registerContentProps, key]);
|
|
15725
|
+
return jsxRuntime.jsx(jsxRuntime.Fragment, { children: typeof children === "function" ? children(close) : children });
|
|
14054
15726
|
}
|
|
14055
15727
|
|
|
14056
15728
|
/**
|
|
@@ -14062,9 +15734,23 @@ function ModalContent({ children, className }) {
|
|
|
14062
15734
|
* era una fila sin gap.
|
|
14063
15735
|
*
|
|
14064
15736
|
* 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.
|
|
15737
|
+
* ganando: `<ModalHeader className="flex flex-col gap-1">` mantiene su columna. El slot
|
|
15738
|
+
* `classNames.header` del `<Modal>` se aplica entre medias (gana a la fachada, pierde con el
|
|
15739
|
+
* `className`), y el nodo lleva el `id` que nombra el diálogo, como en v2.
|
|
14066
15740
|
*/
|
|
14067
|
-
const ModalHeader = ({ className, ...props }) =>
|
|
15741
|
+
const ModalHeader = ({ className, ...props }) => {
|
|
15742
|
+
const { headerId, classNames } = $8WNJc$react.useContext(ModalFacadeContext);
|
|
15743
|
+
return (jsxRuntime.jsx(react.ModalHeader
|
|
15744
|
+
// Nombra el diálogo (`aria-labelledby`, ver `Modal.tsx`). Va antes del spread: un `id`
|
|
15745
|
+
// propio del consumidor gana, igual que en v2 (`id: headerId, ...otherProps`).
|
|
15746
|
+
, {
|
|
15747
|
+
// Nombra el diálogo (`aria-labelledby`, ver `Modal.tsx`). Va antes del spread: un `id`
|
|
15748
|
+
// propio del consumidor gana, igual que en v2 (`id: headerId, ...otherProps`).
|
|
15749
|
+
id: headerId, ...props, className: react.cn("flex-row gap-0 px-6 py-4 text-lg font-semibold",
|
|
15750
|
+
// v2 `classNames.header` del `<Modal>`; el `className` propio gana
|
|
15751
|
+
// (`clsx(classNames.header, className)` en `@heroui/modal@2.2.19`).
|
|
15752
|
+
classNames?.header, className) }));
|
|
15753
|
+
};
|
|
14068
15754
|
ModalHeader.displayName = "ModalHeader";
|
|
14069
15755
|
|
|
14070
15756
|
/**
|
|
@@ -14082,6 +15768,7 @@ ModalHeader.displayName = "ModalHeader";
|
|
|
14082
15768
|
*/
|
|
14083
15769
|
const BODY_V2_GEOMETRY$1 = "mx-0 mt-0 flex flex-1 flex-col gap-3 px-6 py-2";
|
|
14084
15770
|
const ModalBody = $8WNJc$react.forwardRef(({ className, ...props }, forwardedRef) => {
|
|
15771
|
+
const { classNames } = $8WNJc$react.useContext(ModalFacadeContext);
|
|
14085
15772
|
const nodeRef = $8WNJc$react.useRef(null);
|
|
14086
15773
|
const callbackRef = $8WNJc$react.useCallback((node) => {
|
|
14087
15774
|
nodeRef.current = node;
|
|
@@ -14227,7 +15914,10 @@ const ModalBody = $8WNJc$react.forwardRef(({ className, ...props }, forwardedRef
|
|
|
14227
15914
|
track.remove();
|
|
14228
15915
|
};
|
|
14229
15916
|
}, []);
|
|
14230
|
-
return (jsxRuntime.jsx(react.ModalBody, { ref: callbackRef, ...props,
|
|
15917
|
+
return (jsxRuntime.jsx(react.ModalBody, { ref: callbackRef, ...props,
|
|
15918
|
+
// v2 `classNames.body` del `<Modal>` entre la geometría y el `className` propio,
|
|
15919
|
+
// que gana (`clsx(classNames.body, className)` en `@heroui/modal@2.2.19`).
|
|
15920
|
+
className: react.cn(BODY_V2_GEOMETRY$1, classNames?.body, className) }));
|
|
14231
15921
|
});
|
|
14232
15922
|
ModalBody.displayName = "ModalBody";
|
|
14233
15923
|
|
|
@@ -14238,7 +15928,13 @@ ModalBody.displayName = "ModalBody";
|
|
|
14238
15928
|
* `.modal__body + .modal__footer { mt-5 }` que v2 no tenía (v2 separaba los slots solo
|
|
14239
15929
|
* con su propio padding) — de ahí el `mt-0`.
|
|
14240
15930
|
*/
|
|
14241
|
-
const ModalFooter = ({ className, ...props }) =>
|
|
15931
|
+
const ModalFooter = ({ className, ...props }) => {
|
|
15932
|
+
const { classNames } = $8WNJc$react.useContext(ModalFacadeContext);
|
|
15933
|
+
return (jsxRuntime.jsx(react.ModalFooter, { ...props,
|
|
15934
|
+
// v2 `classNames.footer` del `<Modal>` entre la fachada y el `className` propio, que
|
|
15935
|
+
// gana (`clsx(classNames.footer, className)` en `@heroui/modal@2.2.19`).
|
|
15936
|
+
className: react.cn("mt-0 px-6 py-4", classNames?.footer, className) }));
|
|
15937
|
+
};
|
|
14242
15938
|
ModalFooter.displayName = "ModalFooter";
|
|
14243
15939
|
|
|
14244
15940
|
const DrawerFacadeContext = $8WNJc$react.createContext({
|
|
@@ -14472,7 +16168,7 @@ const COLOR_BY_STATUS = {
|
|
|
14472
16168
|
};
|
|
14473
16169
|
// v3 usa `min(32px, var(--radius-3xl))`; v2 `radius="md"` = `rounded-medium` = 12px. Mismos
|
|
14474
16170
|
// valores EXACTOS que Button/Card/Chip (los `rounded-md`/`lg` de Tailwind son 6/8px, no 12/14).
|
|
14475
|
-
const RADIUS_CLASS
|
|
16171
|
+
const RADIUS_CLASS = {
|
|
14476
16172
|
none: "rounded-none",
|
|
14477
16173
|
sm: "rounded-[8px]",
|
|
14478
16174
|
md: "rounded-[12px]",
|
|
@@ -14627,7 +16323,7 @@ function slotClasses(variant, color) {
|
|
|
14627
16323
|
}
|
|
14628
16324
|
// Los subcomponentes compound de v3, adjuntos como estáticos (`Alert.Indicator`…) para que el JSX
|
|
14629
16325
|
// v3 (loginos ya migró el suyo) siga compilando y renderizando IGUAL que contra el `Alert` crudo.
|
|
14630
|
-
const COMPOUND_PARTS
|
|
16326
|
+
const COMPOUND_PARTS = new Set([
|
|
14631
16327
|
react.Alert.Indicator,
|
|
14632
16328
|
react.Alert.Content,
|
|
14633
16329
|
react.Alert.Title,
|
|
@@ -14639,8 +16335,8 @@ const COMPOUND_PARTS$1 = new Set([
|
|
|
14639
16335
|
* hiciera, saldrían dos indicadores). `<Alert>Texto</Alert>` (sin partes compound) sigue siendo
|
|
14640
16336
|
* API v2: children bajo la descripción, con icono y círculo, como en v2.
|
|
14641
16337
|
*/
|
|
14642
|
-
function hasCompoundChildren
|
|
14643
|
-
return $8WNJc$react.Children.toArray(children).some((child) => $8WNJc$react.isValidElement(child) && COMPOUND_PARTS
|
|
16338
|
+
function hasCompoundChildren(children) {
|
|
16339
|
+
return $8WNJc$react.Children.toArray(children).some((child) => $8WNJc$react.isValidElement(child) && COMPOUND_PARTS.has(child.type));
|
|
14644
16340
|
}
|
|
14645
16341
|
/** `isEmpty` de v2 (`@heroui/shared-utils`): null/undefined/"" no cuentan como contenido. */
|
|
14646
16342
|
function hasValue$1(node) {
|
|
@@ -14670,7 +16366,7 @@ const AlertFacade = ({ color, status, variant = "flat", radius = "md", title, de
|
|
|
14670
16366
|
if (!isVisible) {
|
|
14671
16367
|
return null;
|
|
14672
16368
|
}
|
|
14673
|
-
if (hasCompoundChildren
|
|
16369
|
+
if (hasCompoundChildren(children)) {
|
|
14674
16370
|
return (jsxRuntime.jsx(react.Alert, { ...rest, status: effectiveStatus, className: react.cn(classNames?.base, className), children: children }));
|
|
14675
16371
|
}
|
|
14676
16372
|
const handleClose = () => {
|
|
@@ -14690,7 +16386,7 @@ const AlertFacade = ({ color, status, variant = "flat", radius = "md", title, de
|
|
|
14690
16386
|
// Base v2: `flex flex-grow flex-row w-full items-start py-3 px-4 gap-x-1`. v3 ya trae
|
|
14691
16387
|
// flex/w-full/items-start/px-4/py-3; se repone el `gap-x-1` (v3: `gap-4`), se quita el
|
|
14692
16388
|
// `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
|
|
16389
|
+
className: react.cn("grow gap-x-1 shadow-none", slots.base, RADIUS_CLASS[radius], hideIconWrapper && "gap-x-0", classNames?.base, className), children: [startContent, !hideIcon && (jsxRuntime.jsx(react.Alert.Indicator, { className: react.cn(ICON_WRAPPER_BASE,
|
|
14694
16390
|
// v2 `hideIconWrapper`: `!bg-transparent !shadow-none !border-none` — se omiten
|
|
14695
16391
|
// las clases teñidas en vez de pisarlas, porque `bg-transparent` no borraría el
|
|
14696
16392
|
// `dark:bg-*` (grupo distinto para tailwind-merge).
|
|
@@ -14717,181 +16413,82 @@ const Alert = Object.assign(AlertFacade, {
|
|
|
14717
16413
|
Description: react.Alert.Description,
|
|
14718
16414
|
});
|
|
14719
16415
|
|
|
14720
|
-
|
|
14721
|
-
|
|
14722
|
-
|
|
14723
|
-
|
|
14724
|
-
|
|
14725
|
-
|
|
14726
|
-
|
|
14727
|
-
|
|
14728
|
-
|
|
14729
|
-
|
|
14730
|
-
|
|
14731
|
-
|
|
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);
|
|
16416
|
+
const OMIT$1 = new Set(["children", "className", "type"]);
|
|
16417
|
+
const CONCAT = new Set(["aria-describedby", "aria-labelledby"]);
|
|
16418
|
+
function mergeRefs$1(...refs) {
|
|
16419
|
+
return (value) => {
|
|
16420
|
+
for (const ref of refs) {
|
|
16421
|
+
if (typeof ref === "function") {
|
|
16422
|
+
ref(value);
|
|
16423
|
+
}
|
|
16424
|
+
else if (ref) {
|
|
16425
|
+
ref.current = value;
|
|
16426
|
+
}
|
|
16427
|
+
}
|
|
14859
16428
|
};
|
|
14860
|
-
|
|
14861
|
-
|
|
16429
|
+
}
|
|
16430
|
+
function mergeTriggerProps$1(trigger, child) {
|
|
16431
|
+
const merged = { ...trigger, ...child };
|
|
16432
|
+
for (const key of Object.keys(trigger)) {
|
|
16433
|
+
const a = trigger[key];
|
|
16434
|
+
const b = child[key];
|
|
16435
|
+
if (/^on[A-Z]/.test(key) &&
|
|
16436
|
+
typeof a === "function" &&
|
|
16437
|
+
typeof b === "function") {
|
|
16438
|
+
merged[key] = (...args) => {
|
|
16439
|
+
a(...args);
|
|
16440
|
+
b(...args);
|
|
16441
|
+
};
|
|
16442
|
+
}
|
|
16443
|
+
else if (CONCAT.has(key) && a && b) {
|
|
16444
|
+
merged[key] = `${a} ${b}`;
|
|
16445
|
+
}
|
|
16446
|
+
else if (b === undefined) {
|
|
16447
|
+
merged[key] = a;
|
|
16448
|
+
}
|
|
14862
16449
|
}
|
|
14863
|
-
|
|
14864
|
-
|
|
14865
|
-
|
|
14866
|
-
|
|
14867
|
-
|
|
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";
|
|
16450
|
+
return merged;
|
|
16451
|
+
}
|
|
16452
|
+
/** ¿Se puede clonar como trigger? Un único elemento que no sea Fragment. */
|
|
16453
|
+
function isClonableTriggerChild(children) {
|
|
16454
|
+
return $8WNJc$react.isValidElement(children) && children.type !== $8WNJc$react.Fragment;
|
|
16455
|
+
}
|
|
14886
16456
|
/**
|
|
14887
|
-
* `
|
|
14888
|
-
* `
|
|
16457
|
+
* `render` para `Dropdown.Trigger`: clona las props resueltas del `<button>` de v3 sobre `child`.
|
|
16458
|
+
* `triggerClassName` es el `className` que la app puso en el propio `DropdownTrigger`.
|
|
14889
16459
|
*/
|
|
14890
|
-
|
|
14891
|
-
|
|
14892
|
-
|
|
14893
|
-
|
|
14894
|
-
|
|
16460
|
+
function renderDropdownTrigger(child, triggerClassName) {
|
|
16461
|
+
return (buttonProps) => {
|
|
16462
|
+
const { ref, ...rest } = buttonProps;
|
|
16463
|
+
const triggerProps = {};
|
|
16464
|
+
for (const [key, value] of Object.entries(rest)) {
|
|
16465
|
+
if (OMIT$1.has(key) || key.startsWith("data-")) {
|
|
16466
|
+
continue;
|
|
16467
|
+
}
|
|
16468
|
+
triggerProps[key] = value;
|
|
16469
|
+
}
|
|
16470
|
+
const hostTag = typeof child.type === "string" ? child.type : null;
|
|
16471
|
+
if (hostTag === "button") {
|
|
16472
|
+
triggerProps.type = rest.type;
|
|
16473
|
+
}
|
|
16474
|
+
else if (hostTag && hostTag !== "a") {
|
|
16475
|
+
triggerProps.role = "button";
|
|
16476
|
+
triggerProps.tabIndex = 0;
|
|
16477
|
+
}
|
|
16478
|
+
const childRef = (Number.parseInt($8WNJc$react.version, 10) < 19
|
|
16479
|
+
? child.ref
|
|
16480
|
+
: child.props.ref);
|
|
16481
|
+
const childClass = child.props.className;
|
|
16482
|
+
const className = triggerClassName && typeof childClass !== "function"
|
|
16483
|
+
? react.cn(triggerClassName, childClass)
|
|
16484
|
+
: childClass;
|
|
16485
|
+
return $8WNJc$react.cloneElement(child, {
|
|
16486
|
+
...mergeTriggerProps$1(triggerProps, child.props),
|
|
16487
|
+
...(className === undefined ? {} : { className }),
|
|
16488
|
+
ref: mergeRefs$1(childRef, ref),
|
|
16489
|
+
});
|
|
16490
|
+
};
|
|
16491
|
+
}
|
|
14895
16492
|
|
|
14896
16493
|
// TECH-2459 — Fachada `Dropdown` / `DropdownSection`: repone la CABECERA DE SECCIÓN que la
|
|
14897
16494
|
// migración a v3 dejó sin vía legal desde una app.
|
|
@@ -15033,15 +16630,153 @@ const OWN_POPOVER = new Set([
|
|
|
15033
16630
|
function isPart$1(node, parts) {
|
|
15034
16631
|
return $8WNJc$react.isValidElement(node) && parts.has(node.type);
|
|
15035
16632
|
}
|
|
15036
|
-
|
|
15037
|
-
|
|
16633
|
+
function isFragment(node) {
|
|
16634
|
+
return $8WNJc$react.isValidElement(node) && node.type === $8WNJc$react.Fragment;
|
|
16635
|
+
}
|
|
16636
|
+
/**
|
|
16637
|
+
* `Children.toArray` aplana arrays pero NO Fragments (un `<>…</>` cuenta como UN nodo). Esto baja
|
|
16638
|
+
* por ellos, y prefija la `key` de cada hijo con la del Fragment para que dos Fragments hermanos
|
|
16639
|
+
* no produzcan claves repetidas (`toArray` numera cada nivel desde `.0`).
|
|
16640
|
+
*/
|
|
16641
|
+
function flatten(children, prefix = "") {
|
|
16642
|
+
return $8WNJc$react.Children.toArray(children).flatMap((node) => {
|
|
16643
|
+
if (isFragment(node)) {
|
|
16644
|
+
return flatten(node.props.children, `${prefix}${String(node.key)}`);
|
|
16645
|
+
}
|
|
16646
|
+
if (prefix && $8WNJc$react.isValidElement(node)) {
|
|
16647
|
+
return [$8WNJc$react.cloneElement(node, { key: `${prefix}${String(node.key)}` })];
|
|
16648
|
+
}
|
|
16649
|
+
return [node];
|
|
16650
|
+
});
|
|
16651
|
+
}
|
|
16652
|
+
/**
|
|
16653
|
+
* Mapea el árbol conservando su forma: atraviesa Fragments (se reconstruyen con sus hijos
|
|
16654
|
+
* mapeados) y aplica `fn` al resto de nodos. Para el caso en el que el popover es del consumidor:
|
|
16655
|
+
* no hay que mover nada de sitio, solo completar props.
|
|
16656
|
+
*/
|
|
16657
|
+
function mapTree(children, fn) {
|
|
16658
|
+
return $8WNJc$react.Children.toArray(children).map((node) => isFragment(node)
|
|
16659
|
+
? $8WNJc$react.cloneElement(node, undefined, ...mapTree(node.props.children, fn))
|
|
16660
|
+
: fn(node));
|
|
16661
|
+
}
|
|
16662
|
+
/**
|
|
16663
|
+
* Traducción de colocación v2 → v3, copiada del oráculo, no deducida: `@heroui/aria-utils@2.2.29`
|
|
16664
|
+
* → `dist/chunk-WQVQ7P2I.mjs:53-69` (`toReactAriaPlacement`), que es lo que el `Popover` de v2
|
|
16665
|
+
* hacía con la prop antes de dársela a react-aria. Ojo a los laterales: `left-start` NO es
|
|
16666
|
+
* `"left start"` (no existe en react-aria) sino `"left top"`.
|
|
16667
|
+
*/
|
|
16668
|
+
const V2_TO_V3_PLACEMENT = {
|
|
16669
|
+
top: "top",
|
|
16670
|
+
bottom: "bottom",
|
|
16671
|
+
left: "left",
|
|
16672
|
+
right: "right",
|
|
16673
|
+
"top-start": "top start",
|
|
16674
|
+
"top-end": "top end",
|
|
16675
|
+
"bottom-start": "bottom start",
|
|
16676
|
+
"bottom-end": "bottom end",
|
|
16677
|
+
"left-start": "left top",
|
|
16678
|
+
"left-end": "left bottom",
|
|
16679
|
+
"right-start": "right top",
|
|
16680
|
+
"right-end": "right bottom",
|
|
16681
|
+
};
|
|
16682
|
+
/** v2 → v3; lo que ya viene en sintaxis de v3 pasa tal cual. */
|
|
16683
|
+
function toV3Placement(placement) {
|
|
16684
|
+
return (V2_TO_V3_PLACEMENT[placement] ??
|
|
16685
|
+
placement);
|
|
16686
|
+
}
|
|
16687
|
+
/** Solo las claves que el consumidor escribió: `undefined` no pisa nada. */
|
|
16688
|
+
function definedOnly(values) {
|
|
16689
|
+
return Object.fromEntries(Object.entries(values).filter(([, value]) => value !== undefined));
|
|
16690
|
+
}
|
|
16691
|
+
/**
|
|
16692
|
+
* `closeOnSelect` de v2 → `shouldCloseOnSelect` del `Menu` de v3. Precedencia, la de v2
|
|
16693
|
+
* (`@heroui/dropdown@2.3.32` → `dist/chunk-HT5XYLWZ.mjs`, `getCloseOnSelect`): lo del menú gana a
|
|
16694
|
+
* lo del root. Del menú se lee `shouldCloseOnSelect` (v3) y, si no, `closeOnSelect` (v2: 6 de los
|
|
16695
|
+
* 20 `<DropdownMenu>` de beweossmbs lo escriben en el MENÚ, no en el root, y el `Menu` de v3 lo
|
|
16696
|
+
* ignora — sin esto, un multiselect se cerraba en cada clic). El `closeOnSelect` se retira del
|
|
16697
|
+
* elemento: v3 no lo conoce.
|
|
16698
|
+
*/
|
|
16699
|
+
function withCloseOnSelect(node, rootCloseOnSelect) {
|
|
16700
|
+
if (!isPart$1(node, INSIDE_POPOVER)) {
|
|
16701
|
+
return node;
|
|
16702
|
+
}
|
|
16703
|
+
const menu = node;
|
|
16704
|
+
const { closeOnSelect, shouldCloseOnSelect } = menu.props;
|
|
16705
|
+
const resolved = shouldCloseOnSelect ?? closeOnSelect ?? rootCloseOnSelect;
|
|
16706
|
+
if (resolved === undefined && !("closeOnSelect" in menu.props)) {
|
|
16707
|
+
return node;
|
|
16708
|
+
}
|
|
16709
|
+
const { closeOnSelect: _dropped, ...rest } = menu.props;
|
|
16710
|
+
return $8WNJc$react.createElement(menu.type, {
|
|
16711
|
+
...rest,
|
|
16712
|
+
key: menu.key,
|
|
16713
|
+
shouldCloseOnSelect: resolved,
|
|
16714
|
+
});
|
|
16715
|
+
}
|
|
16716
|
+
/**
|
|
16717
|
+
* `<DropdownTrigger><Button/></DropdownTrigger>` de v2 → UN botón, el de la app, con las props del
|
|
16718
|
+
* trigger de v3 clonadas encima vía su `render` (ver `_internal/renderTrigger.tsx`). Sin esto v3
|
|
16719
|
+
* pinta dos `<button>` anidados. Se deja el trigger de v3 tal cual si el consumidor ya pasa su
|
|
16720
|
+
* propio `render`, o si el hijo no es UN elemento (texto, Fragment, función de render props):
|
|
16721
|
+
* entonces el `<button>` de v3 es el trigger, que es lo correcto.
|
|
16722
|
+
*
|
|
16723
|
+
* Medido en beweossmbs `origin/staging` (f6d5be1c2): 20 `<DropdownTrigger>`, 19 con un `<Button>`
|
|
16724
|
+
* de hijo directo y 1 con `{trigger || defaultTrigger}` (también un `Button` en ejecución).
|
|
16725
|
+
*/
|
|
16726
|
+
function withClonedTrigger(node) {
|
|
16727
|
+
if (!$8WNJc$react.isValidElement(node) || node.type !== react.Dropdown.Trigger) {
|
|
16728
|
+
return node;
|
|
16729
|
+
}
|
|
16730
|
+
const trigger = node;
|
|
16731
|
+
const { children, render, className } = trigger.props;
|
|
16732
|
+
if (render !== undefined || !isClonableTriggerChild(children)) {
|
|
16733
|
+
return node;
|
|
16734
|
+
}
|
|
16735
|
+
return $8WNJc$react.cloneElement(trigger, {
|
|
16736
|
+
render: renderDropdownTrigger(children, typeof className === "string" ? className : undefined),
|
|
16737
|
+
});
|
|
16738
|
+
}
|
|
16739
|
+
const DropdownFacade = ({ children, placement, offset, crossOffset, shouldFlip, containerPadding, closeOnSelect, shouldCloseOnScroll: _noEffectInV3, className, classNames, ...props }) => {
|
|
16740
|
+
const overrides = definedOnly({
|
|
16741
|
+
placement: placement === undefined ? undefined : toV3Placement(placement),
|
|
16742
|
+
offset,
|
|
16743
|
+
crossOffset,
|
|
16744
|
+
shouldFlip,
|
|
16745
|
+
containerPadding,
|
|
16746
|
+
});
|
|
16747
|
+
// v2: `base` (caja exterior), luego `content` + `className` (caja interior); en v3 es una sola
|
|
16748
|
+
// caja, así que van juntas y en ese orden. `cn` funde conflictos entre ellas; frente a la clase
|
|
16749
|
+
// de v3 (`.dropdown__popover`, capa `components`) cualquier utilidad ya gana por capa.
|
|
16750
|
+
const popoverClass = react.cn(classNames?.base, classNames?.content, className) || undefined;
|
|
16751
|
+
const menuWithClose = (node) => withCloseOnSelect(node, closeOnSelect);
|
|
16752
|
+
const nodes = flatten(children);
|
|
16753
|
+
// El popover es del consumidor: se respeta su árbol entero y se le COMPLETA lo que no dijo
|
|
16754
|
+
// (lo que sí dijo manda). Los menús dentro de él reciben el `closeOnSelect` igualmente.
|
|
16755
|
+
if (nodes.some((node) => isPart$1(node, OWN_POPOVER))) {
|
|
16756
|
+
const fillPopover = (node) => {
|
|
16757
|
+
if (!$8WNJc$react.isValidElement(node) || node.type !== react.Dropdown.Popover) {
|
|
16758
|
+
return node;
|
|
16759
|
+
}
|
|
16760
|
+
const own = node;
|
|
16761
|
+
const missing = Object.fromEntries(Object.entries(overrides).filter(([key]) => own.props[key] === undefined));
|
|
16762
|
+
const ownClass = own.props.className;
|
|
16763
|
+
return $8WNJc$react.cloneElement(own, {
|
|
16764
|
+
...missing,
|
|
16765
|
+
...(popoverClass && typeof ownClass !== "function"
|
|
16766
|
+
? { className: react.cn(popoverClass, ownClass) }
|
|
16767
|
+
: {}),
|
|
16768
|
+
}, ...mapTree(own.props.children, menuWithClose));
|
|
16769
|
+
};
|
|
16770
|
+
return (jsxRuntime.jsx(react.Dropdown, { ...props, children: mapTree(children, (node) => fillPopover(withClonedTrigger(node))) }));
|
|
16771
|
+
}
|
|
15038
16772
|
const menus = nodes.filter((node) => isPart$1(node, INSIDE_POPOVER));
|
|
15039
|
-
// Nada que envolver (sin hijos
|
|
15040
|
-
|
|
15041
|
-
|
|
15042
|
-
return jsxRuntime.jsx(react.Dropdown, { ...props, children: children });
|
|
16773
|
+
// Nada que envolver (sin hijos o ningún menú suelto): el root tal cual, como el crudo de v3.
|
|
16774
|
+
if (menus.length === 0) {
|
|
16775
|
+
return (jsxRuntime.jsx(react.Dropdown, { ...props, children: mapTree(children, withClonedTrigger) }));
|
|
15043
16776
|
}
|
|
15044
|
-
return (jsxRuntime.jsxs(react.Dropdown, { ...props, children: [nodes
|
|
16777
|
+
return (jsxRuntime.jsxs(react.Dropdown, { ...props, children: [nodes
|
|
16778
|
+
.filter((node) => !isPart$1(node, INSIDE_POPOVER))
|
|
16779
|
+
.map(withClonedTrigger), jsxRuntime.jsx(react.Dropdown.Popover, { offset: 7, placement: "bottom", ...overrides, className: popoverClass, children: menus.map(menuWithClose) })] }));
|
|
15045
16780
|
};
|
|
15046
16781
|
DropdownFacade.displayName = "Aurora.Dropdown";
|
|
15047
16782
|
/**
|
|
@@ -15475,6 +17210,87 @@ const Tabs = Object.assign(TabsFacade, {
|
|
|
15475
17210
|
Tab: TabFacade,
|
|
15476
17211
|
});
|
|
15477
17212
|
|
|
17213
|
+
const OMIT = new Set([
|
|
17214
|
+
"role",
|
|
17215
|
+
"tabIndex",
|
|
17216
|
+
"className",
|
|
17217
|
+
"data-slot",
|
|
17218
|
+
"children",
|
|
17219
|
+
]);
|
|
17220
|
+
function mergeRefs(...refs) {
|
|
17221
|
+
return (value) => {
|
|
17222
|
+
for (const ref of refs) {
|
|
17223
|
+
if (typeof ref === "function") {
|
|
17224
|
+
ref(value);
|
|
17225
|
+
}
|
|
17226
|
+
else if (ref) {
|
|
17227
|
+
ref.current = value;
|
|
17228
|
+
}
|
|
17229
|
+
}
|
|
17230
|
+
};
|
|
17231
|
+
}
|
|
17232
|
+
/** `mergeProps` de v2: handlers `on*` encadenados (trigger → hijo), el resto gana el hijo. */
|
|
17233
|
+
function mergeTriggerProps(trigger, child) {
|
|
17234
|
+
const merged = { ...trigger, ...child };
|
|
17235
|
+
for (const key of Object.keys(trigger)) {
|
|
17236
|
+
const a = trigger[key];
|
|
17237
|
+
const b = child[key];
|
|
17238
|
+
if (/^on[A-Z]/.test(key) &&
|
|
17239
|
+
typeof a === "function" &&
|
|
17240
|
+
typeof b === "function") {
|
|
17241
|
+
merged[key] = (...args) => {
|
|
17242
|
+
a(...args);
|
|
17243
|
+
b(...args);
|
|
17244
|
+
};
|
|
17245
|
+
}
|
|
17246
|
+
else if (key === "aria-describedby" && a && b) {
|
|
17247
|
+
merged[key] = `${a} ${b}`;
|
|
17248
|
+
}
|
|
17249
|
+
else if (b === undefined) {
|
|
17250
|
+
merged[key] = a;
|
|
17251
|
+
}
|
|
17252
|
+
}
|
|
17253
|
+
return merged;
|
|
17254
|
+
}
|
|
17255
|
+
function renderTooltipTrigger(children) {
|
|
17256
|
+
return (divProps) => {
|
|
17257
|
+
const { ref, ...rest } = divProps;
|
|
17258
|
+
const triggerProps = {};
|
|
17259
|
+
for (const [key, value] of Object.entries(rest)) {
|
|
17260
|
+
if (!OMIT.has(key)) {
|
|
17261
|
+
triggerProps[key] = value;
|
|
17262
|
+
}
|
|
17263
|
+
}
|
|
17264
|
+
const single = $8WNJc$react.Children.count(children) === 1 && $8WNJc$react.isValidElement(children);
|
|
17265
|
+
if (single && children.type !== $8WNJc$react.Fragment) {
|
|
17266
|
+
const child = children;
|
|
17267
|
+
const childRef = (Number.parseInt($8WNJc$react.version, 10) < 19
|
|
17268
|
+
? child.ref
|
|
17269
|
+
: child.props.ref);
|
|
17270
|
+
return $8WNJc$react.cloneElement(child, {
|
|
17271
|
+
...mergeTriggerProps(triggerProps, child.props),
|
|
17272
|
+
ref: mergeRefs(childRef, ref),
|
|
17273
|
+
});
|
|
17274
|
+
}
|
|
17275
|
+
return (jsxRuntime.jsx("div", { ...triggerProps, ref: ref, className: rest.className, "data-slot": "tooltip-trigger", children: children }));
|
|
17276
|
+
};
|
|
17277
|
+
}
|
|
17278
|
+
|
|
17279
|
+
/**
|
|
17280
|
+
* `color` de v2 (`@heroui/theme@2.4.15`, `popover` → `colorVariants.solid`): fondo y texto del
|
|
17281
|
+
* color en la caja, y la flecha del mismo color (v2 la pintaba con `before:bg-*`; en v3 es un SVG
|
|
17282
|
+
* con `fill: var(--overlay)`, así que se pisa su `fill`). `default` no añade nada: se ve como v3
|
|
17283
|
+
* (§9.1). Clases LITERALES — Tailwind 4 solo genera lo que está escrito en el fuente.
|
|
17284
|
+
*/
|
|
17285
|
+
const COLOR_CLASSES = {
|
|
17286
|
+
default: "",
|
|
17287
|
+
foreground: "bg-foreground text-background [&_[data-slot=overlay-arrow]]:fill-foreground",
|
|
17288
|
+
primary: "bg-primary text-primary-foreground [&_[data-slot=overlay-arrow]]:fill-primary",
|
|
17289
|
+
secondary: "bg-secondary text-secondary-foreground [&_[data-slot=overlay-arrow]]:fill-secondary",
|
|
17290
|
+
success: "bg-success text-success-foreground [&_[data-slot=overlay-arrow]]:fill-success",
|
|
17291
|
+
warning: "bg-warning text-warning-foreground [&_[data-slot=overlay-arrow]]:fill-warning",
|
|
17292
|
+
danger: "bg-danger text-danger-foreground [&_[data-slot=overlay-arrow]]:fill-danger",
|
|
17293
|
+
};
|
|
15478
17294
|
/**
|
|
15479
17295
|
* Tooltip - Componente BeweOS basado en HeroUI.
|
|
15480
17296
|
*
|
|
@@ -15490,7 +17306,11 @@ const Tabs = Object.assign(TabsFacade, {
|
|
|
15490
17306
|
const Tooltip = ({ enableTapOnTouch = true, touchPlacement = "top", placement, offset, showArrow = false, content, isOpen, defaultOpen, onOpenChange, isDisabled,
|
|
15491
17307
|
// v3 cambia el default de 0 (v2) a 700 — se mantiene 0 para no regresionar el
|
|
15492
17308
|
// comportamiento instantáneo que ya tenía v2 (ver Tooltip.types.ts).
|
|
15493
|
-
delay = 0, closeDelay, trigger, className, children, }) => {
|
|
17309
|
+
delay = 0, closeDelay, trigger, className, classNames, color = "default", children, }) => {
|
|
17310
|
+
// v3 tiene un solo nodo donde v2 tenía `base` + `content` (ver `TooltipClassNames`). Orden de
|
|
17311
|
+
// v2: color → `classNames` → `className`; `cn` fusiona para que la clase de la app gane.
|
|
17312
|
+
const contentClassName = react.cn(COLOR_CLASSES[color] ?? "", classNames?.base, classNames?.content, className);
|
|
17313
|
+
const arrow = showArrow && (jsxRuntime.jsx(react.Tooltip.Arrow, { className: classNames?.arrow }));
|
|
15494
17314
|
// Detección táctil: NO basta con `(hover: none)`. Muchos móviles (p.ej.
|
|
15495
17315
|
// Android) reportan `(hover: hover)` y `(hover: none)` da false, dejando el
|
|
15496
17316
|
// tap sin activar. Se combina con `(pointer: coarse)` (coincide si cualquiera
|
|
@@ -15528,7 +17348,7 @@ delay = 0, closeDelay, trigger, className, children, }) => {
|
|
|
15528
17348
|
}, [tapMode, open, setOpen]);
|
|
15529
17349
|
// Desktop (o tap deshabilitado) → comportamiento nativo de HeroUI.
|
|
15530
17350
|
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, {
|
|
17351
|
+
return (jsxRuntime.jsxs(react.Tooltip, { isOpen: isOpen, defaultOpen: defaultOpen, onOpenChange: onOpenChange, isDisabled: isDisabled, delay: delay, closeDelay: closeDelay, trigger: trigger, children: [jsxRuntime.jsx(react.Tooltip.Trigger, { render: renderTooltipTrigger(children) }), jsxRuntime.jsxs(react.Tooltip.Content, { placement: placement, offset: offset, showArrow: showArrow, className: contentClassName, children: [arrow, content] })] }));
|
|
15532
17352
|
}
|
|
15533
17353
|
// Táctil → controlado, el tap sobre el trigger alterna la visibilidad.
|
|
15534
17354
|
// El placement cambia a `touchPlacement` (arriba por defecto) para que no
|
|
@@ -15540,7 +17360,7 @@ delay = 0, closeDelay, trigger, className, children, }) => {
|
|
|
15540
17360
|
return (jsxRuntime.jsxs(react.Tooltip, { isOpen: open, onOpenChange: setOpen, isDisabled: isDisabled, children: [jsxRuntime.jsx(react.Tooltip.Trigger, { children: jsxRuntime.jsx("span", { ref: triggerRef, className: "inline-flex", onPointerUp: (event) => {
|
|
15541
17361
|
event.stopPropagation();
|
|
15542
17362
|
setOpen(!open);
|
|
15543
|
-
}, children: children }) }), jsxRuntime.jsxs(react.Tooltip.Content, { placement: touchPlacement, offset: offset, showArrow: showArrow, className:
|
|
17363
|
+
}, children: children }) }), jsxRuntime.jsxs(react.Tooltip.Content, { placement: touchPlacement, offset: offset, showArrow: showArrow, className: contentClassName, children: [arrow, content] })] }));
|
|
15544
17364
|
};
|
|
15545
17365
|
|
|
15546
17366
|
/**
|