@beweco/aurora-ui 1.0.0-beta.150 → 1.0.0-beta.155

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs.js CHANGED
@@ -8113,18 +8113,49 @@ const MenuComponent = $8WNJc$react.memo(function Menu({ commerceInfo, userInfo,
8113
8113
  color: "default", className: " text-default-500 data-[hovered=true]:text-default-600", startContent: jsxRuntime.jsx(IconComponent, { className: "text-default-500", icon: "solar:info-circle-line-duotone", size: "md" }), variant: "light", onPress: handleHelpClick, isIconOnly: isCollapsed, children: !isCollapsed && (jsxRuntime.jsx("span", { className: "collapsible-item truncate", children: helpSection.title })) }) })) : null] }) })] }));
8114
8114
  });
8115
8115
 
8116
+ /**
8117
+ * «Cargar más» solo se ofrece si hay otra página, alguien que pueda pedirla y la lista
8118
+ * ya enseña todo lo recibido (desplegada, o sin ítems plegados tras «Ver más»).
8119
+ */
8120
+ function isLoadMoreVisible({ hasMore, hasOnLoadMore, isExpanded, hasHiddenItems, }) {
8121
+ return hasMore && hasOnLoadMore && (isExpanded || !hasHiddenItems);
8122
+ }
8123
+ /**
8124
+ * Pulsación de «Cargar más». Con una página en vuelo no hace nada: el botón no usa
8125
+ * `disabled` nativo (perdería el foco del teclado), así que la guarda vive aquí.
8126
+ * Si no, despliega la lista y pide la página, para que lo nuevo no quede tras «Ver más».
8127
+ */
8128
+ function pressLoadMore({ isLoadingMore, onLoadMore, expand, }) {
8129
+ if (isLoadingMore) {
8130
+ return;
8131
+ }
8132
+ expand();
8133
+ onLoadMore();
8134
+ }
8135
+
8116
8136
  /**
8117
8137
  * Sección de historial de conversaciones: botón de nueva conversación + lista de
8118
- * entradas recientes con "ver más".
8138
+ * entradas recientes con "ver más" y, si el consumidor tiene más páginas,
8139
+ * "cargar más".
8119
8140
  *
8120
8141
  * La lista tiene **scroll interno propio** (`flex-1 min-h-0 overflow-y-auto`) y
8121
8142
  * la sección está acotada en altura, para que crecer el historial no arrastre ni
8122
8143
  * recorte el área de navegación.
8123
8144
  *
8124
- * Densidad de MenuClaw: fila de 36px, `px-2`, `text-small`/`text-tiny` y radios
8125
- * `rounded-lg`/`rounded-xl` — que es justo lo que difiere del menú compartido.
8145
+ * Densidad de MenuClaw: fila de 36px, `px-2`, `text-small`/`text-tiny` y filas del
8146
+ * historial con `rounded-lg` — que es justo lo que difiere del menú compartido.
8147
+ *
8148
+ * «Nueva conversación» es el `Button` de la librería, no un `<button>` propio, y
8149
+ * por eso lleva su radio por defecto (`rounded-[24px]`, la píldora de v3): sobre
8150
+ * 36px de alto el radio se recorta a la mitad, así que desplegado es una píldora
8151
+ * y plegado (`isIconOnly`, 36×36) un círculo. Encima de la fachada solo se fija lo
8152
+ * propio de MenuClaw: el alto de 36px (`size="md"` de la fachada mide 40), el
8153
+ * contenido alineado a la izquierda con `px-2`/`gap-3`, el peso 500 y el color
8154
+ * `bg-foreground`/`text-background`, que la fachada no tiene como `color` y que
8155
+ * gana a sus `--button-*` porque las utilidades van en una capa posterior a la de
8156
+ * `.button`.
8126
8157
  */
8127
- const MenuClawHistoryPanel = $8WNJc$react.memo(function MenuClawHistoryPanel({ items, initialVisibleCount, onNewConversation, isCollapsed, title, newLabel, showMoreLabel, showLessLabel, onBeforeSelect, }) {
8158
+ const MenuClawHistoryPanel = $8WNJc$react.memo(function MenuClawHistoryPanel({ items, initialVisibleCount, onNewConversation, hasMore, onLoadMore, isLoadingMore, loadMoreLabel, isCollapsed, title, newLabel, showMoreLabel, showLessLabel, onBeforeSelect, }) {
8128
8159
  const [isExpanded, setIsExpanded] = $8WNJc$react.useState(false);
8129
8160
  const visibleItems = $8WNJc$react.useMemo(() => (isExpanded ? items : items.slice(0, initialVisibleCount)), [isExpanded, items, initialVisibleCount]);
8130
8161
  const hasHiddenItems = items.length > initialVisibleCount;
@@ -8145,11 +8176,23 @@ const MenuClawHistoryPanel = $8WNJc$react.memo(function MenuClawHistoryPanel({ i
8145
8176
  if (isCollapsed && !onNewConversation) {
8146
8177
  return null;
8147
8178
  }
8148
- return (jsxRuntime.jsxs("div", { className: "flex max-h-[40%] min-h-0 flex-col gap-2 pb-3", children: [onNewConversation ? (jsxRuntime.jsxs("button", { type: "button", title: newLabel, onClick: onNewConversation, className: cn("flex h-9 shrink-0 items-center gap-3 rounded-xl bg-foreground px-2 text-sm font-medium text-background transition-opacity hover:opacity-90", "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus", isCollapsed && "justify-center"), children: [jsxRuntime.jsx("span", { className: "flex h-5 w-5 shrink-0 items-center justify-center", children: jsxRuntime.jsx(IconComponent, { icon: "solar:pen-new-square-linear", size: "md", "aria-hidden": true }) }), !isCollapsed ? (jsxRuntime.jsx("span", { className: "menu-claw-collapsible truncate", children: newLabel })) : null] })) : null, !isCollapsed && items.length > 0 ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("p", { className: "shrink-0 px-2 pb-1 pt-2 text-xs font-medium uppercase tracking-wide text-default-400", children: title }), jsxRuntime.jsx("ul", { "aria-label": title, className: "flex min-h-0 flex-1 flex-col gap-0.5 overflow-y-auto [&::-webkit-scrollbar]:hidden [-ms-overflow-style:none] [scrollbar-width:none]", children: visibleItems.map((item) => (jsxRuntime.jsx("li", { children: jsxRuntime.jsxs("button", { type: "button", disabled: item.disabled, "aria-disabled": item.disabled, title: item.title, onClick: () => handleItemPress(item), className: cn("flex min-h-9 w-full items-center gap-3 rounded-lg px-2 py-1.5 text-left text-sm text-default-600 transition-colors", "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus", item.disabled
8179
+ return (jsxRuntime.jsxs("div", { className: "flex max-h-[40%] min-h-0 flex-col gap-2 pb-3", children: [onNewConversation ? (jsxRuntime.jsxs(Button, { title: newLabel, "aria-label": isCollapsed ? newLabel : undefined, isIconOnly: isCollapsed, fullWidth: !isCollapsed, onPress: () => onNewConversation(), className: cn("h-9 shrink-0 bg-foreground text-sm font-medium text-background", isCollapsed
8180
+ ? "w-9 min-w-9 self-center"
8181
+ : "justify-start gap-3 px-2"), children: [jsxRuntime.jsx("span", { className: "flex h-5 w-5 shrink-0 items-center justify-center", children: jsxRuntime.jsx(IconComponent, { icon: "solar:pen-new-square-linear", size: "md", "aria-hidden": true }) }), !isCollapsed ? (jsxRuntime.jsx("span", { className: "menu-claw-collapsible truncate", children: newLabel })) : null] })) : null, !isCollapsed && items.length > 0 ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("p", { className: "shrink-0 px-2 pb-1 pt-2 text-xs font-medium uppercase tracking-wide text-default-400", children: title }), jsxRuntime.jsx("ul", { "aria-label": title, className: "flex min-h-0 flex-1 flex-col gap-0.5 overflow-y-auto [&::-webkit-scrollbar]:hidden [-ms-overflow-style:none] [scrollbar-width:none]", children: visibleItems.map((item) => (jsxRuntime.jsx("li", { children: jsxRuntime.jsxs("button", { type: "button", disabled: item.disabled, "aria-disabled": item.disabled, title: item.title, onClick: () => handleItemPress(item), className: cn("flex min-h-9 w-full items-center gap-3 rounded-lg px-2 py-1.5 text-left text-sm text-default-600 transition-colors", "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus", item.disabled
8149
8182
  ? "cursor-not-allowed opacity-50"
8150
8183
  : "hover:bg-default-100"), children: [jsxRuntime.jsx("span", { className: "flex h-5 w-5 shrink-0 items-center justify-center text-default-400", children: jsxRuntime.jsx(IconComponent, { icon: "solar:chat-round-dots-linear", size: "md", "aria-hidden": true }) }), jsxRuntime.jsxs("span", { className: "flex min-w-0 flex-col", children: [jsxRuntime.jsx("span", { className: "truncate", children: item.title }), item.meta ? (jsxRuntime.jsx("span", { className: "truncate text-xs text-default-400", children: item.meta })) : null] })] }) }, item.id))) }), hasHiddenItems ? (jsxRuntime.jsxs("button", { type: "button", onClick: handleToggleExpand, "aria-expanded": isExpanded, className: "flex w-full shrink-0 items-center gap-1.5 rounded-lg px-2 py-2 text-xs font-medium text-default-500 transition-colors hover:bg-default-100 hover:text-default-700 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus", children: [jsxRuntime.jsx(IconComponent, { icon: isExpanded
8151
8184
  ? "solar:alt-arrow-up-linear"
8152
- : "solar:alt-arrow-down-linear", size: "sm", "aria-hidden": true }), isExpanded ? showLessLabel : showMoreLabel] })) : null] })) : null] }));
8185
+ : "solar:alt-arrow-down-linear", size: "sm", "aria-hidden": true }), isExpanded ? showLessLabel : showMoreLabel] })) : null, onLoadMore &&
8186
+ isLoadMoreVisible({
8187
+ hasMore,
8188
+ hasOnLoadMore: true,
8189
+ isExpanded,
8190
+ hasHiddenItems,
8191
+ }) ? (jsxRuntime.jsxs("button", { type: "button", onClick: () => pressLoadMore({
8192
+ isLoadingMore,
8193
+ onLoadMore,
8194
+ expand: () => setIsExpanded(true),
8195
+ }), "aria-disabled": isLoadingMore, "aria-busy": isLoadingMore, className: "flex w-full shrink-0 items-center gap-1.5 rounded-lg px-2 py-2 text-xs font-medium text-default-500 transition-colors hover:bg-default-100 hover:text-default-700 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus aria-disabled:cursor-not-allowed aria-disabled:opacity-50", children: [jsxRuntime.jsx(IconComponent, { icon: "solar:add-circle-linear", size: "sm", "aria-hidden": true }), loadMoreLabel] })) : null] })) : null] }));
8153
8196
  });
8154
8197
 
8155
8198
  /** ¿Está la key seleccionada en este ítem o en cualquiera de sus descendientes? */
@@ -8213,6 +8256,7 @@ const DEFAULT_TRANSLATIONS$a = {
8213
8256
  historyNewLabel: "Nueva conversación",
8214
8257
  historyShowMoreLabel: "Ver más",
8215
8258
  historyShowLessLabel: "Ver menos",
8259
+ historyLoadMoreLabel: "Cargar más",
8216
8260
  };
8217
8261
  /** Ítems del historial visibles antes de pulsar "ver más". */
8218
8262
  const DEFAULT_HISTORY_VISIBLE_COUNT = 5;
@@ -8271,7 +8315,12 @@ const MenuClaw = $8WNJc$react.memo(function MenuClaw({ userInfo, helpButton, men
8271
8315
  const initialVisibleCount = typeof rawCount === "number" && Number.isFinite(rawCount) && rawCount > 0
8272
8316
  ? Math.floor(rawCount)
8273
8317
  : DEFAULT_HISTORY_VISIBLE_COUNT;
8274
- return { items, initialVisibleCount };
8318
+ return {
8319
+ items,
8320
+ initialVisibleCount,
8321
+ hasMore: history?.hasMore === true,
8322
+ isLoadingMore: history?.isLoadingMore === true,
8323
+ };
8275
8324
  }, [history]);
8276
8325
  const bottomBarItems = $8WNJc$react.useMemo(() => {
8277
8326
  if (!mobileBottomBarGroupKey) {
@@ -8316,7 +8365,7 @@ const MenuClaw = $8WNJc$react.memo(function MenuClaw({ userInfo, helpButton, men
8316
8365
  ? "solar:alt-arrow-right-linear"
8317
8366
  : "solar:alt-arrow-left-linear", size: "md", "aria-hidden": true }) })] }), jsxRuntime.jsxs("div", { className: cn("flex items-center py-3", collapsed ? "justify-center gap-0" : "gap-3"), children: [jsxRuntime.jsxs(react.Avatar, { size: "sm", className: "shrink-0", children: [userInfo?.avatar?.trim() ? (jsxRuntime.jsx(react.Avatar.Image, { src: userInfo.avatar, alt: userName })) : null, jsxRuntime.jsx(react.Avatar.Fallback, { className: userInfo?.avatar?.trim()
8318
8367
  ? undefined
8319
- : "bg-warning text-warning-foreground", children: getUserInitials(userName) })] }), !collapsed ? (jsxRuntime.jsxs("div", { className: "menu-claw-collapsible flex min-w-0 flex-col overflow-hidden", children: [jsxRuntime.jsx("p", { className: "truncate text-sm font-semibold text-foreground", children: userName }), jsxRuntime.jsx("p", { className: "truncate text-xs text-default-400", children: userRole })] })) : null] }), jsxRuntime.jsx(MenuClawHistoryPanel, { items: historyConfig.items, initialVisibleCount: historyConfig.initialVisibleCount, onNewConversation: history?.onNewConversation, isCollapsed: collapsed, title: t.historyTitle, newLabel: t.historyNewLabel, showMoreLabel: t.historyShowMoreLabel, showLessLabel: t.historyShowLessLabel, onBeforeSelect: handleSidebarClose }), jsxRuntime.jsx("nav", { className: "flex min-h-0 flex-1 flex-col overflow-y-auto [&::-webkit-scrollbar]:hidden [-ms-overflow-style:none] [scrollbar-width:none]", children: menuItems.items.map((group) => (jsxRuntime.jsxs("div", { className: "mb-2", children: [group.title && !collapsed ? (jsxRuntime.jsx("p", { className: "px-2 pb-1 pt-2 text-xs font-medium uppercase tracking-wide text-default-400", children: group.title })) : null, jsxRuntime.jsx("ul", { className: "flex flex-col gap-0.5", children: (group.items ?? []).map((item) => (jsxRuntime.jsx(MenuClawNavItem, { item: item, selectedKey: selectedKey, isCollapsed: collapsed, onSelect: handleMenuSelect }, item.key))) })] }, group.key))) }), helpSection ? (jsxRuntime.jsx("div", { className: "mt-auto flex flex-col items-center justify-center", children: jsxRuntime.jsx(Button, { fullWidth: true,
8368
+ : "bg-warning text-warning-foreground", children: getUserInitials(userName) })] }), !collapsed ? (jsxRuntime.jsxs("div", { className: "menu-claw-collapsible flex min-w-0 flex-col overflow-hidden", children: [jsxRuntime.jsx("p", { className: "truncate text-sm font-semibold text-foreground", children: userName }), jsxRuntime.jsx("p", { className: "truncate text-xs text-default-400", children: userRole })] })) : null] }), jsxRuntime.jsx(MenuClawHistoryPanel, { items: historyConfig.items, initialVisibleCount: historyConfig.initialVisibleCount, onNewConversation: history?.onNewConversation, hasMore: historyConfig.hasMore, onLoadMore: history?.onLoadMore, isLoadingMore: historyConfig.isLoadingMore, isCollapsed: collapsed, title: t.historyTitle, newLabel: t.historyNewLabel, showMoreLabel: t.historyShowMoreLabel, showLessLabel: t.historyShowLessLabel, loadMoreLabel: t.historyLoadMoreLabel, onBeforeSelect: handleSidebarClose }), jsxRuntime.jsx("nav", { className: "flex min-h-0 flex-1 flex-col overflow-y-auto [&::-webkit-scrollbar]:hidden [-ms-overflow-style:none] [scrollbar-width:none]", children: menuItems.items.map((group) => (jsxRuntime.jsxs("div", { className: "mb-2", children: [group.title && !collapsed ? (jsxRuntime.jsx("p", { className: "px-2 pb-1 pt-2 text-xs font-medium uppercase tracking-wide text-default-400", children: group.title })) : null, jsxRuntime.jsx("ul", { className: "flex flex-col gap-0.5", children: (group.items ?? []).map((item) => (jsxRuntime.jsx(MenuClawNavItem, { item: item, selectedKey: selectedKey, isCollapsed: collapsed, onSelect: handleMenuSelect }, item.key))) })] }, group.key))) }), helpSection ? (jsxRuntime.jsx("div", { className: "mt-auto flex flex-col items-center justify-center", children: jsxRuntime.jsx(Button, { fullWidth: true,
8320
8369
  // TECH-2459 · G2 — la fachada propia de `Button` (D-G4-1) pinta
8321
8370
  // `color="primary"` por defecto; el crudo de v2 pintaba "default"
8322
8371
  // (gris). Se fija explícito para no teñir de azul un botón neutro.
@@ -12209,7 +12258,7 @@ function measureTextareaHeight(node, minRows, maxRows) {
12209
12258
  };
12210
12259
  }
12211
12260
  // `useLayoutEffect` en el servidor avisa en React 18; el alto solo tiene sentido en el cliente.
12212
- const useIsomorphicLayoutEffect = typeof window !== "undefined" ? $8WNJc$react.useLayoutEffect : $8WNJc$react.useEffect;
12261
+ const useIsomorphicLayoutEffect$1 = typeof window !== "undefined" ? $8WNJc$react.useLayoutEffect : $8WNJc$react.useEffect;
12213
12262
  /**
12214
12263
  * Autocrecimiento del `<textarea>` apuntado por `nodeRef`. Devuelve el `resize` para llamarlo
12215
12264
  * desde el `onChange` de la fachada.
@@ -12239,11 +12288,11 @@ function useTextareaAutosize(nodeRef, { enabled, minRows, maxRows }) {
12239
12288
  const resizeRef = $8WNJc$react.useRef(resize);
12240
12289
  resizeRef.current = resize;
12241
12290
  // Cada render, como v2: el valor controlado, el `size` o la clase pueden haber cambiado.
12242
- useIsomorphicLayoutEffect(() => {
12291
+ useIsomorphicLayoutEffect$1(() => {
12243
12292
  resize();
12244
12293
  });
12245
12294
  // Al apagar el autosize, se retira lo que se escribió a mano para que mande el CSS.
12246
- useIsomorphicLayoutEffect(() => {
12295
+ useIsomorphicLayoutEffect$1(() => {
12247
12296
  const node = nodeRef.current;
12248
12297
  if (enabled || !node) {
12249
12298
  return;
@@ -12691,6 +12740,68 @@ const useAuraToast = () => {
12691
12740
  return context;
12692
12741
  };
12693
12742
 
12743
+ const ToastHostContext = $8WNJc$react.createContext(null);
12744
+ // `useLayoutEffect` para que el cambio de quién pinta se resuelva antes de pintar (sin un frame
12745
+ // con cero o con dos toasts); en el servidor avisa en React 18, y ahí no hay nada que pintar.
12746
+ const useIsomorphicLayoutEffect = typeof window !== "undefined" ? $8WNJc$react.useLayoutEffect : $8WNJc$react.useEffect;
12747
+ function pickActive(hosts) {
12748
+ let best = null;
12749
+ for (const entry of hosts) {
12750
+ if (best === null ||
12751
+ entry[1].priority > best[1].priority ||
12752
+ (entry[1].priority === best[1].priority && entry[1].seq < best[1].seq)) {
12753
+ best = entry;
12754
+ }
12755
+ }
12756
+ return best ? best[0] : null;
12757
+ }
12758
+ /** Estado del registro, para el provider. */
12759
+ function useToastHostRegistry() {
12760
+ const [hosts, setHosts] = $8WNJc$react.useState(() => new Map());
12761
+ const seqRef = $8WNJc$react.useRef(0);
12762
+ const register = $8WNJc$react.useCallback((id, priority) => {
12763
+ seqRef.current += 1;
12764
+ const seq = seqRef.current;
12765
+ setHosts((prev) => {
12766
+ const next = new Map(prev);
12767
+ next.set(id, { priority, seq });
12768
+ return next;
12769
+ });
12770
+ return () => {
12771
+ setHosts((prev) => {
12772
+ // Solo borra SU registro: si ya lo sustituyó otro con el mismo id, no lo toca.
12773
+ if (prev.get(id)?.seq !== seq) {
12774
+ return prev;
12775
+ }
12776
+ const next = new Map(prev);
12777
+ next.delete(id);
12778
+ return next;
12779
+ });
12780
+ };
12781
+ }, []);
12782
+ const activeId = $8WNJc$react.useMemo(() => pickActive(hosts), [hosts]);
12783
+ return $8WNJc$react.useMemo(() => ({ activeId, register }), [activeId, register]);
12784
+ }
12785
+ /**
12786
+ * Registra la instancia y dice si le toca pintar. Sin provider (sin registro) devuelve `true`:
12787
+ * el comportamiento de antes, que decide `useAuraToast`.
12788
+ */
12789
+ function useIsActiveToastHost(priority) {
12790
+ const registry = $8WNJc$react.useContext(ToastHostContext);
12791
+ const id = $8WNJc$react.useId();
12792
+ const register = registry?.register;
12793
+ useIsomorphicLayoutEffect(() => {
12794
+ if (!register) {
12795
+ return;
12796
+ }
12797
+ return register(id, priority);
12798
+ }, [register, id, priority]);
12799
+ if (!registry) {
12800
+ return true;
12801
+ }
12802
+ return registry.activeId === id;
12803
+ }
12804
+
12694
12805
  const VARIANT_BY_COLOR = {
12695
12806
  primary: "accent",
12696
12807
  secondary: "default",
@@ -12706,13 +12817,28 @@ const VARIANT_BY_COLOR = {
12706
12817
  // de `.toast` (`bg-surface`: blanco en claro, la superficie oscura del pack en oscuro) en los seis
12707
12818
  // colores, y el color del estado va SOLO en el título y en el icono. La descripción es la de v3
12708
12819
  // (`text-muted`), sin tocar.
12709
- // La `x`, neutra en los seis colores: `text-muted`, el mismo gris de `.close-button--default` de
12710
- // v3, que llega solo al no pisarlo. `bg-transparent` quita el `bg-default` de esa clase: en Aurora
12711
- // `--color-default` es el gris MEDIO de v2 (`#d4d4d8`), no el casi imperceptible que v3 espera, y
12712
- // sobre el fondo blanco cantaba como una píldora gris permanente. El hover sí lleva el gris suave
12713
- // de v3 (`bg-default-hover`, que deriva del `--default` PROPIO de v3, no del de Aurora); hay que
12714
- // declararlo como utilidad porque el `bg-transparent` de arriba también gana al hover de v3.
12715
- const CLOSE_CLASS = "shrink-0 self-center bg-transparent hover:bg-default-hover data-[hovered=true]:bg-default-hover";
12820
+ // TECH-2459 · Geometría de v3 — DECISIÓN DEL USUARIO del 2-oct-2026, tras ver la .149/.150 en la
12821
+ // app: «déjalos tal cual se ven en la v3 de HeroUI». Al aspecto del 1-oct (colores) se suma ahora
12822
+ // la geometría: radio, caja del icono, hueco, alto y botón de cierre son los de `.toast` de v3 sin
12823
+ // pisar nada (`toast.css` de `@heroui/styles`, última aparición de cada selector):
12824
+ // - radio: `min(32px, var(--radius-3xl))` de `.toast` (24px con el tema de v3);
12825
+ // - padding `px-4 py-3` y `gap-1.5` (6px) entre icono y contenido, de `.toast`;
12826
+ // - icono: `.toast__indicator` (`p-1`) con el glifo a `size-4` por `[data-slot="toast-default-icon"]`,
12827
+ // así que el glifo lleva ese `data-slot`, igual que el que monta el `ToastIndicator` nativo;
12828
+ // - contenido: `.toast__content` (`self-center`), sin suelo de alto: lo dan el padding y el texto;
12829
+ // - la `x`: `.toast__close-button` (slot `close` de `toastVariants`), un círculo de 20px absoluto en
12830
+ // la esquina superior final (`-top-1 -end-1`, sobresale 4px), con borde y fondo `overlay` desde
12831
+ // `sm` y sin borde y fondo `default` por debajo; oculto en reposo y revelado por
12832
+ // `.toast[data-frontmost="true"]:hover` — el flag `data-frontmost` de abajo hace que esa regla
12833
+ // nativa aplique aquí también. v3 no tiene regla para `(hover: none)`: en táctil el primer toque
12834
+ // sobre el toast activa el `:hover` emulado y revela la `x`, y el segundo la pulsa (medido igual
12835
+ // en el nativo).
12836
+ //
12837
+ // Lo ÚNICO que se añade a v3 es el revelado con teclado (`REVEAL_ON_FOCUS`): v3 no tiene ninguna
12838
+ // regla de `:focus`/`:focus-visible`/`:focus-within` que lo haga visible, así que con Tab el foco
12839
+ // caía en un botón con `opacity: 0` (medido en el `Toast` nativo). Es accesibilidad, no aspecto: en
12840
+ // reposo y con ratón el botón se comporta exactamente como el nativo.
12841
+ const REVEAL_ON_FOCUS = "data-[focus-visible=true]:pointer-events-auto data-[focus-visible=true]:opacity-100";
12716
12842
  // TECH-2459 · Indicador de estado — REPUESTO el 8-sep-2026 por decisión del usuario, a partir del
12717
12843
  // finding `app-loginos-toast-v3-visual.md` (smoke de loginos contra QA, beta.114): con el MISMO
12718
12844
  // toast (`color="danger"`, login con credenciales inválidas) v2 pintaba un glifo de estado a la
@@ -12759,20 +12885,29 @@ const ICON_BY_COLOR = {
12759
12885
  danger: "",
12760
12886
  default: "",
12761
12887
  };
12762
- // Geometría del hueco del icono en v2 (slot `iconWrapper`: `flex-none relative w-9 h-9
12763
- // rounded-full grid place-items-center`, con el glifo a `w-6`). El toast de v2 pasaba
12764
- // `hideIconWrapper`, que solo dejaba transparente el círculo (`!bg-transparent !shadow-none
12765
- // !border-none`) sin ocultar el glifo, así que aquí se repone la caja de 36 px y el glifo de
12766
- // 24 px, sin fondo ni borde. `p-0` y `[&_svg]:size-6` anulan el `p-1` + icono de 16 px de
12767
- // `.toast__indicator`; son utilidades, así que ganan a la capa `components` de `@heroui/styles`.
12768
- const INDICATOR_BASE = "size-9 p-0 [&_svg]:size-6 [&_svg]:fill-current";
12769
12888
  const DEFAULT_TRANSLATIONS$6 = {
12770
12889
  closeButtonAriaLabel: "Cerrar notificación",
12771
12890
  };
12772
- function GlobalToast({ translations = {} } = {}) {
12891
+ /**
12892
+ * Toast global de la app. Idempotente: si bajo el mismo `AuraToastProvider` hay más de uno
12893
+ * montado (el que el provider monta por dentro + el que la app pone en su layout), pinta solo
12894
+ * uno — el de la app, y entre varios de la app el que recibe `translations` (ver
12895
+ * `_internal/toast-host.ts`).
12896
+ */
12897
+ function GlobalToast({ translations } = {}) {
12898
+ const priority = translations ? 2 : 1;
12899
+ return jsxRuntime.jsx(ToastSlot, { translations: translations, priority: priority });
12900
+ }
12901
+ /** El `GlobalToast` que `AuraToastProvider` monta por dentro: el de menor prioridad. */
12902
+ function ProviderToast() {
12903
+ return jsxRuntime.jsx(ToastSlot, { priority: 0 });
12904
+ }
12905
+ function ToastSlot({ translations = {}, priority, }) {
12906
+ // Sin provider, `useAuraToast` lanza igual que antes (no se cambia ese contrato).
12773
12907
  const { toast, hideToast } = useAuraToast();
12908
+ const isActive = useIsActiveToastHost(priority);
12774
12909
  const t = { ...DEFAULT_TRANSLATIONS$6, ...translations };
12775
- if (!toast.isVisible) {
12910
+ if (!(toast.isVisible && isActive)) {
12776
12911
  return null;
12777
12912
  }
12778
12913
  const color = toast.color ?? "primary";
@@ -12782,7 +12917,7 @@ function GlobalToast({ translations = {} } = {}) {
12782
12917
  const slots = react.toastVariants({ placement: "top end", variant });
12783
12918
  const isSecondary = color === "secondary";
12784
12919
  const StatusIcon = ICON_BY_VARIANT[variant];
12785
- return (jsxRuntime.jsx("div", { className: "fixed top-4 right-4 z-[2000] w-full max-w-sm", children: jsxRuntime.jsxs("div", { className: cn(slots.toast(), "gap-x-0 rounded-[12px]"), "data-frontmost": "true", style: { position: "relative" }, children: [jsxRuntime.jsx("div", { className: cn(slots.indicator(), INDICATOR_BASE, ICON_BY_COLOR[color]), children: jsxRuntime.jsx(StatusIcon, {}) }), jsxRuntime.jsxs("div", { className: cn(slots.content(), "ms-2 box-border min-h-10 justify-center"), children: [jsxRuntime.jsx("div", { className: slots.title(), style: isSecondary ? { color: "var(--color-secondary-600)" } : undefined, children: toast.title }), toast.description && (jsxRuntime.jsx("div", { className: slots.description(), children: toast.description }))] }), jsxRuntime.jsx(react.CloseButton, { "aria-label": t.closeButtonAriaLabel, className: CLOSE_CLASS, onPress: hideToast })] }) }));
12920
+ return (jsxRuntime.jsx("div", { className: "fixed top-4 right-4 z-[2000] w-full max-w-sm", children: jsxRuntime.jsxs("div", { className: slots.toast(), "data-frontmost": "true", style: { position: "relative" }, children: [jsxRuntime.jsx("div", { className: cn(slots.indicator(), ICON_BY_COLOR[color]), children: jsxRuntime.jsx(StatusIcon, { "data-slot": "toast-default-icon" }) }), jsxRuntime.jsxs("div", { className: slots.content(), children: [jsxRuntime.jsx("div", { className: slots.title(), style: isSecondary ? { color: "var(--color-secondary-600)" } : undefined, children: toast.title }), toast.description && (jsxRuntime.jsx("div", { className: slots.description(), children: toast.description }))] }), jsxRuntime.jsx(react.CloseButton, { "aria-label": t.closeButtonAriaLabel, className: cn(slots.close(), REVEAL_ON_FOCUS), onPress: hideToast })] }) }));
12786
12921
  }
12787
12922
 
12788
12923
  /**
@@ -19532,6 +19667,8 @@ const AuraToastProvider = ({ children }) => {
19532
19667
  color: "primary",
19533
19668
  duration: 8000,
19534
19669
  });
19670
+ // Registro de los `GlobalToast` montados debajo, para que pinte uno solo (ver toast-host.ts).
19671
+ const toastHosts = useToastHostRegistry();
19535
19672
  const timerRef = $8WNJc$react.useRef(null);
19536
19673
  const hideToast = $8WNJc$react.useCallback(() => {
19537
19674
  if (timerRef.current) {
@@ -19559,7 +19696,7 @@ const AuraToastProvider = ({ children }) => {
19559
19696
  }
19560
19697
  };
19561
19698
  }, []);
19562
- return (jsxRuntime.jsxs(ToastContext.Provider, { value: { toast, showToast, hideToast }, children: [jsxRuntime.jsx(GlobalToast, {}), children] }));
19699
+ return (jsxRuntime.jsx(ToastContext.Provider, { value: { toast, showToast, hideToast }, children: jsxRuntime.jsxs(ToastHostContext.Provider, { value: toastHosts, children: [jsxRuntime.jsx(ProviderToast, {}), children] }) }));
19563
19700
  };
19564
19701
 
19565
19702
  const NavigationLoadingContext = $8WNJc$react.createContext(undefined);