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