beez-ui 2.6.2 → 2.7.1

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/CHANGELOG.md CHANGED
@@ -4,6 +4,35 @@ Todos los cambios relevantes de beez-ui se documentan en este archivo con el for
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [2.7.1] - 2026-10-16
8
+
9
+ ### Fixed
10
+
11
+ - `RichLinkEditor` ya no pinta su propio fondo: deja ver la superficie que lo contiene, así dentro de un diálogo o de una tarjeta en modo oscuro deja de verse como un bloque de otro color.
12
+
13
+ ## [2.7.0] - 2026-10-16
14
+
15
+ ### Added
16
+
17
+ - `beez-ui/motion-tokens` suma los presets de Arc que faltaban: `MOTION_TIMING.instant`, `MOTION_TIMING.standard`, `MOTION_TIMING.considered`, `MOTION_EASE_EXIT`, `MOTION_EASE_STANDARD`, `MOTION_EXIT_SCALE`, `MOTION_TOOLTIP_SCALE` y `MOTION_DIALOG_EXIT_DISTANCE`.
18
+
19
+ ### Changed
20
+
21
+ - Los tokens de `beez-ui/motion-tokens` toman los valores de los presets de Arc y conservan sus nombres: los springs pasan a `snappy`, `smooth` o `morph` (expresados con `visualDuration` y `bounce` en lugar de `stiffness`, `damping` y `mass`), las duraciones y curvas usan las de Arc y la presión baja a 0.97.
22
+ - `Dialog` y `AlertDialog` entran como el diálogo de Arc: el panel sube 8 px y crece desde 0.96 con un resorte suave mientras aparece, y sale rápido (0.16 s), en lugar de desplegarse desde el centro. La transformación y la opacidad que el panel recibe por `className` o `style` se mantienen durante toda la animación, y reabrirlo mientras sale lo devuelve desde donde está.
23
+ - `Input`, `Textarea`, `InputGroup` y `SelectTrigger` ya no tiemblan al pasar a `aria-invalid="true"`: el error se indica con el color y el mensaje, sin mover el campo.
24
+ - Las opciones de `Select`, `Alert`, `FormMessage` y el ícono de `AnimatedThemeToggler` aparecen con un fundido y una escala breves, sin desenfoque; las opciones del `Select` entran todas juntas, sin escalonarse.
25
+ - El ancho del `Sidebar` cambia con el resorte `smooth` de Arc (0.4 s, sin rebote), más suave que antes.
26
+
27
+ ### Deprecated
28
+
29
+ - `MOTION_TIMING.centerMorph`, `MOTION_TIMING.centerMorphFold`, `MOTION_EASE_UNFOLD` y `MOTION_CENTER_MORPH_FOLDED_PERCENT`: los diálogos ya no se despliegan desde el centro. Siguen exportados con su valor.
30
+ - `MOTION_TIMING.shake` y `MOTION_INVALID_SHAKE_PX`: los campos inválidos ya no tiemblan. Siguen exportados con su valor.
31
+
32
+ ### Fixed
33
+
34
+ - Al moverse con el teclado mientras un `Select` o un `DropdownMenu` todavía se está abriendo, el resaltado se desliza hasta el tamaño y la posición finales de la opción y ya no salta al terminar.
35
+
7
36
  ## [2.6.2] - 2026-10-16
8
37
 
9
38
  ### Fixed
package/README.md CHANGED
@@ -71,47 +71,67 @@ Los `Sheet` superiores e inferiores limitan su altura al viewport dinámico (`10
71
71
 
72
72
  ## Movimiento
73
73
 
74
- Las nuevas animaciones usan la dependencia `motion`: `animate` controla la
75
- reproducción, `hover` y `press` los gestos, y `AnimatePresence` / `usePresence`
76
- retienen los overlays durante su salida. Los presets y tiempos viven en
77
- `src/motion/`. No se usan keyframes CSS para estas animaciones.
74
+ El movimiento sigue a [Arc](https://uiarc.dev): explica causa y efecto (qué se abrió,
75
+ qué cambió, de dónde vino) y no decora. Si una animación no explica nada, no está.
76
+ Las animaciones usan la dependencia `motion`: `animate` controla la reproducción,
77
+ `hover` y `press` los gestos, y `AnimatePresence` / `usePresence` retienen los
78
+ overlays durante su salida. Los presets y tiempos viven en `src/motion/`. No se usan
79
+ keyframes CSS para estas animaciones.
78
80
 
79
81
  `MotionSlot` anima el nodo original del primitive mediante refs compuestos,
80
- sin agregar wrappers al DOM ni reemplazar sus handlers. Las curvas y springs
81
- siguen los tokens de [beui motion](https://beui.dev/components/motion):
82
-
83
- - `Button` usa la presión de Arc: se comprime a 0.97 con un spring rápido (un poco más en
82
+ sin agregar wrappers al DOM ni reemplazar sus handlers. Los tokens exportados en
83
+ `beez-ui/motion-tokens` toman los valores de los presets de Arc
84
+ (`components/arc/lib/motion-tokens.ts`):
85
+
86
+ - Springs: `snappy` (0.26 s, rebote 0.12) para presiones, indicadores chicos y cambios de
87
+ ícono (`SPRING_PRESS`, `SPRING_POP`, `SPRING_CHEVRON`, `SPRING_HIGHLIGHT`, `SPRING_TOOLTIP`);
88
+ `smooth` (0.4 s, sin rebote) para paneles y todo lo que informa un estado
89
+ (`SPRING_PANEL`, `SPRING_THUMB`); `morph` (0.42 s, rebote 0.16) para resaltados compartidos
90
+ (`SPRING_LAYOUT`, `SPRING_TABS`).
91
+ - Duraciones: `instant` 0.12 s, `fast` 0.16 s, `exit` 0.18 s, `enter`/`standard` 0.24 s y
92
+ `considered` 0.48 s. Las salidas son más cortas que las entradas.
93
+ - Curvas: `MOTION_EASE` (entrada), `MOTION_EASE_EXIT`, `MOTION_EASE_STANDARD` y
94
+ `MOTION_EASE_IN_OUT`.
95
+
96
+ Los nombres de los tokens se mantienen entre versiones; los del despliegue desde el centro
97
+ y del temblor de campos inválidos quedan deprecados y conservan su valor.
98
+
99
+ - `Button` usa la presión de Arc: se comprime a 0.97 con el spring `snappy` (un poco más en
84
100
  botones de solo ícono y menos en los anchos), sin escalar los disparadores de menús; su
85
101
  texto cambia de ancho con un spring. Los ítems de `Sidebar` y `Tabs` se comprimen a 0.98.
86
102
  - `Checkbox` y `RadioGroup` se comprimen a 0.92. La marca del checkbox aparece
87
- con pop y trazo (`pathLength`) y sale con blur; el punto del radio aparece con spring.
88
- - El thumb de `Switch` viaja con un spring pesado y se aplasta mientras se presiona.
89
- - `Input`, `Textarea`, `InputGroup` y `SelectTrigger` tiemblan al pasar a
90
- `aria-invalid="true"`; `FormMessage` entra con blur.
91
- - `Tooltip` entra con blur, escala y desplazamiento desde el trigger.
92
- `Popover`, `HoverCard`, `DropdownMenu` y `Select` escalan desde 0.96 y se revelan
93
- con un clip desde la esquina más cercana al trigger.
94
- - `Dialog` y `AlertDialog` se despliegan desde su centro y se pliegan al cerrar; `Sheet` entra desde su borde con spring
95
- y sale con la curva de cajón (0.24 s), sin demorar la siguiente interacción.
103
+ con pop y trazo (`pathLength`); el punto del radio aparece con spring.
104
+ - El thumb de `Switch` viaja con el spring `smooth`, sin rebote, y se aplasta mientras se presiona.
105
+ - `Input`, `Textarea`, `InputGroup` y `SelectTrigger` no se mueven al pasar a
106
+ `aria-invalid="true"`: el error se indica con el color y el mensaje. `FormMessage` y
107
+ `Alert` aparecen con un fundido y una escala breves (desde 0.98), sin desenfoque.
108
+ - `Tooltip` sube 3 px desde 0.97 mientras aparece y sale en el lugar, sin desenfoque.
109
+ `Popover` y `HoverCard` se acercan desde su trigger con escala y fundido; `DropdownMenu`
110
+ y `Select` escalan desde 0.96 y se revelan con un clip desde la esquina más cercana al trigger.
111
+ - `Dialog` y `AlertDialog` entran como el diálogo de Arc: el fondo se oscurece en 0.24 s y el
112
+ panel sube 8 px y crece desde 0.96 con el spring `smooth` mientras aparece en 0.16 s; al
113
+ cerrar baja 4 px hasta 0.98 y se desvanece en 0.16 s, y una reapertura parte de donde
114
+ está. `Sheet` entra desde su borde con spring y sale con la curva de cajón (0.24 s), sin
115
+ demorar la siguiente interacción.
96
116
  - `GlideSlot` desliza el indicador activo entre ítems como un `layoutId` compartido:
97
117
  la pestaña activa de `Tabs` (fondo o subrayado), la
98
118
  página actual de `PaginationContent`, y al navegar con teclado el ítem resaltado de
99
119
  `DropdownMenu` y `Select` y la sugerencia activa de `FilterQueryBar`. Con el puntero,
100
120
  esos resaltados lo siguen al instante: el propio puntero ya marca la posición.
101
- - Las superficies flotantes se revelan con un clip que deja 24 px libres fuera de la
121
+ - Las superficies que se revelan con clip dejan 24 px libres fuera de la
102
122
  caja, así su borde (`ring`) y su sombra se ven desde el primer frame. Una copia
103
123
  transitoria viaja entre ítems y se elimina al llegar, devolviendo el estilo al ítem.
104
124
  - El chevron de `SelectTrigger` gira 180° al abrir y los chevrons de `DataTable`
105
125
  (grupos y columnas) giran en lugar de cambiar de ícono; CSS define el ángulo final.
106
- - Las opciones del `Select` entran escalonadas con blur (las primeras 10; el resto
107
- solo se desvanece) y los checks de ítems de `Select` y `DropdownMenu` aparecen con pop.
108
- - `Sidebar` sigue a [beui animated sidebar](https://beui.dev/components/motion/animated-sidebar):
109
- el ancho cambia con un spring entre el panel y el riel de íconos, las etiquetas se
110
- desvanecen al colapsar, una píldora compartida sigue al ítem activo y al hovereado, los
111
- submenús se despliegan escalonados y en pantallas chicas se abre como hoja con foco atrapado.
112
- - El ícono de `AnimatedThemeToggler` cambia con blur y escala; el botón de cierre de
113
- `Dialog` aparece con un pop demorado.
114
- - El contenido de `Tabs` entra con un desplazamiento de 4 px y `Alert` entra con blur.
126
+ - Las opciones del `Select` entran todas juntas con un fundido y una escala breves, sin
127
+ escalonarse ni desenfocarse, y los checks de ítems de `Select` y `DropdownMenu` aparecen con pop.
128
+ - El ancho de `Sidebar` cambia con el spring `smooth` entre el panel y el riel de íconos, las
129
+ etiquetas se desvanecen al colapsar, una píldora compartida sigue al ítem activo y al
130
+ hovereado, los submenús se despliegan escalonados y en pantallas chicas se abre como hoja
131
+ con foco atrapado.
132
+ - El ícono de `AnimatedThemeToggler` crece desde 0.6 con el spring `snappy` mientras aparece,
133
+ sin desenfoque; el botón de cierre de `Dialog` aparece con un pop demorado.
134
+ - El contenido de `Tabs` entra con un desplazamiento de 4 px.
115
135
  Cards y enlaces responden al hover; el avatar bajo el puntero de un `AvatarGroup` se
116
136
  eleva. Un brillo recorre el `Skeleton` desde su `::after` con reproducción nativa,
117
137
  sin trabajo por frame en JavaScript.
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Confirmation dialog on Arc's Dialog surface (https://uiarc.dev, MIT License, see THIRD_PARTY_NOTICES.md): the same
3
- * overlay, raised panel, title and description type as `Dialog`, so both read as one family. It keeps beez-ui's center
4
- * morph entrance, media slot and Arc `Button` actions.
3
+ * overlay, raised panel, title and description type and dialog transition as `Dialog`, so both read as one family. It
4
+ * keeps beez-ui's media slot and Arc `Button` actions.
5
5
  */
6
6
  import * as React from "react";
7
7
  import { AlertDialog as AlertDialogPrimitive } from "radix-ui";
@@ -2,8 +2,8 @@
2
2
  import { c as _c } from "react/compiler-runtime";
3
3
  /**
4
4
  * Confirmation dialog on Arc's Dialog surface (https://uiarc.dev, MIT License, see THIRD_PARTY_NOTICES.md): the same
5
- * overlay, raised panel, title and description type as `Dialog`, so both read as one family. It keeps beez-ui's center
6
- * morph entrance, media slot and Arc `Button` actions.
5
+ * overlay, raised panel, title and description type and dialog transition as `Dialog`, so both read as one family. It
6
+ * keeps beez-ui's media slot and Arc `Button` actions.
7
7
  */
8
8
  import * as React from "react";
9
9
  import { AlertDialog as AlertDialogPrimitive } from "radix-ui";
@@ -180,7 +180,7 @@ function AlertDialogContent(t0) {
180
180
  let t5;
181
181
  if ($[7] !== props || $[8] !== size || $[9] !== t4) {
182
182
  t5 = /* @__PURE__ */ _jsxs(React.Fragment, { children: [t3, /* @__PURE__ */ _jsx(MotionSlot, {
183
- kind: "center-morph",
183
+ kind: "dialog",
184
184
  children: /* @__PURE__ */ _jsx(AlertDialogPrimitive.Content, {
185
185
  forceMount: true,
186
186
  "data-slot": "alert-dialog-content",
@@ -4,7 +4,7 @@ import { c as _c } from "react/compiler-runtime";
4
4
  * Arc's Dialog surface (https://uiarc.dev, MIT License, see THIRD_PARTY_NOTICES.md), shared by `Dialog` and
5
5
  * `AlertDialog`: the dimmed, blurred overlay, the raised panel with Arc's surface radius, border and floating shadow,
6
6
  * the title and description type, the bordered close button and the text swap that animates a title or description
7
- * changed while the dialog is open. Internal: the entrance stays beez-ui's center morph, not Arc's rise.
7
+ * changed while the dialog is open. Internal: the panel's entrance and exit are Arc's, played by `MotionSlot`.
8
8
  */
9
9
  import { AnimatePresence, motion, useIsPresent } from "motion/react";
10
10
  import { DIALOG_TEXT_SWAP_OFFSET_EM } from "../constants/dialog.js";
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * Arc's Dialog (https://uiarc.dev, MIT License, see THIRD_PARTY_NOTICES.md): a focused surface for decisions that need
3
3
  * attention, on Arc's overlay, raised panel, title and description type and bordered close button, with Arc's swap of
4
- * a title or description changed while open. It keeps beez-ui's composable parts, the center morph entrance and the
5
- * viewport close button placement instead of Arc's single `title`/`description` content and rise entrance.
4
+ * a title or description changed while open, and Arc's dialog transition: the panel rises and scales in on the smooth
5
+ * spring and leaves quickly. It keeps beez-ui's composable parts and the viewport close button placement instead of
6
+ * Arc's single `title`/`description` content.
6
7
  */
7
8
  import * as React from "react";
8
9
  import { Dialog as DialogPrimitive } from "radix-ui";
@@ -14,8 +15,8 @@ declare function DialogOverlay({ className, ...props }: React.ComponentProps<typ
14
15
  /** Where the dialog close button sits: inside the panel or pinned to the viewport corner. */
15
16
  type DialogCloseButtonPlacement = "content" | "viewport";
16
17
  /**
17
- * Renders compact dialog content with viewport-safe scrolling. The panel unfolds from its
18
- * exact center toward every edge and folds back the same way when it closes.
18
+ * Renders compact dialog content with viewport-safe scrolling. The panel rises 8px and scales
19
+ * from 0.96 on Arc's smooth spring while it fades in, and leaves with a short drop and fade.
19
20
  * @param props - Primitive properties and optional consumer overrides.
20
21
  * @param props.showCloseButton - Whether the dialog renders its own close button.
21
22
  * @param props.closeButtonPlacement - `viewport` pins the close button to the viewport corner
@@ -3,8 +3,9 @@ import { c as _c } from "react/compiler-runtime";
3
3
  /**
4
4
  * Arc's Dialog (https://uiarc.dev, MIT License, see THIRD_PARTY_NOTICES.md): a focused surface for decisions that need
5
5
  * attention, on Arc's overlay, raised panel, title and description type and bordered close button, with Arc's swap of
6
- * a title or description changed while open. It keeps beez-ui's composable parts, the center morph entrance and the
7
- * viewport close button placement instead of Arc's single `title`/`description` content and rise entrance.
6
+ * a title or description changed while open, and Arc's dialog transition: the panel rises and scales in on the smooth
7
+ * spring and leaves quickly. It keeps beez-ui's composable parts and the viewport close button placement instead of
8
+ * Arc's single `title`/`description` content.
8
9
  */
9
10
  import * as React from "react";
10
11
  import { Dialog as DialogPrimitive } from "radix-ui";
@@ -165,7 +166,7 @@ function DialogOverlay(t0) {
165
166
  }
166
167
  /**
167
168
  * Renders a close button pinned to the viewport corner. It stays hidden while the panel
168
- * unfolds, because the entrance clip would otherwise cut it with the panel, and
169
+ * enters, because the entrance transform would otherwise carry it along with the panel, and
169
170
  * fades in once the panel settles. It hides at once when the dialog starts closing.
170
171
  * While hidden it is also `visibility: hidden`, so the dialog's initial focus and the
171
172
  * keyboard can never land on a control that cannot be seen.
@@ -202,8 +203,8 @@ function DialogViewportCloseButton() {
202
203
  return t1;
203
204
  }
204
205
  /**
205
- * Renders compact dialog content with viewport-safe scrolling. The panel unfolds from its
206
- * exact center toward every edge and folds back the same way when it closes.
206
+ * Renders compact dialog content with viewport-safe scrolling. The panel rises 8px and scales
207
+ * from 0.96 on Arc's smooth spring while it fades in, and leaves with a short drop and fade.
207
208
  * @param props - Primitive properties and optional consumer overrides.
208
209
  * @param props.showCloseButton - Whether the dialog renders its own close button.
209
210
  * @param props.closeButtonPlacement - `viewport` pins the close button to the viewport corner
@@ -300,7 +301,7 @@ function DialogContent(t0) {
300
301
  let t11;
301
302
  if ($[18] !== children || $[19] !== props || $[20] !== t10 || $[21] !== t5 || $[22] !== t7 || $[23] !== t8 || $[24] !== t9) {
302
303
  t11 = /* @__PURE__ */ _jsxs(React.Fragment, { children: [t4, /* @__PURE__ */ _jsx(MotionSlot, {
303
- kind: "center-morph",
304
+ kind: "dialog",
304
305
  children: /* @__PURE__ */ _jsxs(DialogPrimitive.Content, {
305
306
  forceMount: true,
306
307
  "data-slot": "dialog-content",
@@ -20,7 +20,7 @@ const LINK_REL = "noreferrer";
20
20
  * text below 16px, so the editor and the inputs are pinned to 1rem on phones.
21
21
  */
22
22
  const RICH_LINK_EDITOR_CLASS_NAMES = {
23
- editor: "min-h-28 w-full cursor-text border-0 bg-background p-0 leading-[1.65] whitespace-pre-wrap text-foreground [font:inherit] [overflow-wrap:anywhere] focus:outline-0 empty:before:pointer-events-none empty:before:text-muted-foreground/75 empty:before:content-[attr(data-placeholder)] max-md:text-base",
23
+ editor: "min-h-28 w-full cursor-text border-0 bg-transparent p-0 leading-[1.65] whitespace-pre-wrap text-foreground [font:inherit] [overflow-wrap:anywhere] focus:outline-0 empty:before:pointer-events-none empty:before:text-muted-foreground/75 empty:before:content-[attr(data-placeholder)] max-md:text-base",
24
24
  editorInvalid: "rounded-[0.35rem] shadow-[inset_0_-2px_0_var(--destructive)] outline-0",
25
25
  editorLink: "cursor-pointer rounded-[0.2rem] font-medium text-primary underline decoration-[0.08em] underline-offset-[0.18em] transition-colors hover:text-[color-mix(in_oklab,var(--primary)_72%,var(--foreground))] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring",
26
26
  popover: "gap-3",
@@ -20,7 +20,7 @@ import { SIDEBAR_COOKIE_MAX_AGE, SIDEBAR_COOKIE_NAME } from "../constants/sideba
20
20
  import { cn } from "../lib/utils.js";
21
21
  import { MotionSlot } from "../motion/motion-slot.js";
22
22
  import { SharedLayoutBg } from "../motion/shared-layout-bg.js";
23
- import { MOTION_EASE, MOTION_EASE_DRAWER, MOTION_LIST_ITEM_DISTANCE, MOTION_TIMING, SPRING_LAYOUT } from "../motion/tokens.js";
23
+ import { MOTION_EASE, MOTION_EASE_DRAWER, MOTION_LIST_ITEM_DISTANCE, MOTION_TIMING, SPRING_LAYOUT, SPRING_PANEL } from "../motion/tokens.js";
24
24
  import { Link } from "./link.js";
25
25
  import { SidebarPanelContext } from "./sidebar-panel-context.js";
26
26
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
@@ -42,15 +42,10 @@ const PANEL_TRANSITION = {
42
42
  ease: MOTION_EASE_DRAWER
43
43
  };
44
44
  /**
45
- * The desktop rail settles at a hard zero-width boundary. Keep the spring critically damped so it
46
- * cannot overshoot, pause against that boundary, and then snap back during the final frame.
45
+ * The desktop rail settles at a hard zero-width boundary, and its width reports the panel state. Arc's smooth spring
46
+ * is critically damped, so it cannot overshoot, pause against that boundary and snap back during the final frame.
47
47
  */
48
- const SIDEBAR_MORPH_TRANSITION = {
49
- type: "spring",
50
- stiffness: 380,
51
- damping: 35,
52
- mass: .75
53
- };
48
+ const SIDEBAR_MORPH_TRANSITION = SPRING_PANEL;
54
49
  const LABEL_ENTER_TRANSITION = {
55
50
  duration: MOTION_TIMING.message,
56
51
  delay: .08,
@@ -8,11 +8,14 @@ export declare const ARC_DURATION: {
8
8
  readonly fast: 0.16;
9
9
  readonly exit: 0.18;
10
10
  readonly standard: 0.24;
11
+ readonly considered: 0.48;
11
12
  };
12
- /** Cubic-bezier curves for entrances and color or opacity tweens. */
13
+ /** Cubic-bezier curves: entrances, exits, color or opacity tweens, and moves of elements already on screen. */
13
14
  export declare const ARC_EASE: {
14
15
  readonly enter: readonly [0.16, 1, 0.3, 1];
16
+ readonly exit: readonly [0.7, 0, 0.84, 0];
15
17
  readonly standard: readonly [0.22, 1, 0.36, 1];
18
+ readonly inOut: readonly [0.65, 0, 0.35, 1];
16
19
  };
17
20
  /** Presses, toggles and small indicators: settles fast with a hint of life. */
18
21
  export declare const ARC_SPRING_SNAPPY: {
@@ -35,6 +38,7 @@ export declare const ARC_SPRING_MORPH: {
35
38
  /** Stagger steps in seconds; keep a whole staggered entrance under roughly 0.4s. */
36
39
  export declare const ARC_STAGGER: {
37
40
  readonly char: 0.016;
41
+ readonly item: 0.035;
38
42
  };
39
43
  /** Blur radii in pixels for brief content crossfades. */
40
44
  export declare const ARC_BLUR: {
@@ -7,9 +7,10 @@ export const ARC_DURATION = {
7
7
  instant: .12,
8
8
  fast: .16,
9
9
  exit: .18,
10
- standard: .24
10
+ standard: .24,
11
+ considered: .48
11
12
  };
12
- /** Cubic-bezier curves for entrances and color or opacity tweens. */
13
+ /** Cubic-bezier curves: entrances, exits, color or opacity tweens, and moves of elements already on screen. */
13
14
  export const ARC_EASE = {
14
15
  enter: [
15
16
  .16,
@@ -17,11 +18,23 @@ export const ARC_EASE = {
17
18
  .3,
18
19
  1
19
20
  ],
21
+ exit: [
22
+ .7,
23
+ 0,
24
+ .84,
25
+ 0
26
+ ],
20
27
  standard: [
21
28
  .22,
22
29
  1,
23
30
  .36,
24
31
  1
32
+ ],
33
+ inOut: [
34
+ .65,
35
+ 0,
36
+ .35,
37
+ 1
25
38
  ]
26
39
  };
27
40
  /** Presses, toggles and small indicators: settles fast with a hint of life. */
@@ -43,7 +56,10 @@ export const ARC_SPRING_MORPH = {
43
56
  bounce: .16
44
57
  };
45
58
  /** Stagger steps in seconds; keep a whole staggered entrance under roughly 0.4s. */
46
- export const ARC_STAGGER = { char: .016 };
59
+ export const ARC_STAGGER = {
60
+ char: .016,
61
+ item: .035
62
+ };
47
63
  /** Blur radii in pixels for brief content crossfades. */
48
64
  export const ARC_BLUR = {
49
65
  subtle: 2,
@@ -55,9 +55,8 @@ export declare const GLIDE_PRESETS: {
55
55
  surface: () => "background";
56
56
  spring: {
57
57
  readonly type: "spring";
58
- readonly stiffness: 700;
59
- readonly damping: 45;
60
- readonly mass: 0.5;
58
+ readonly visualDuration: 0.26;
59
+ readonly bounce: 0.12;
61
60
  };
62
61
  instantOnPointer: true;
63
62
  };
@@ -69,9 +68,8 @@ export declare const GLIDE_PRESETS: {
69
68
  surface: () => "background";
70
69
  spring: {
71
70
  readonly type: "spring";
72
- readonly stiffness: 700;
73
- readonly damping: 45;
74
- readonly mass: 0.5;
71
+ readonly visualDuration: 0.26;
72
+ readonly bounce: 0.12;
75
73
  };
76
74
  instantOnPointer: true;
77
75
  };
@@ -47,42 +47,81 @@ export const GLIDE_PRESETS = {
47
47
  export const GLIDE_TARGET_ATTRIBUTE = "data-glide-target";
48
48
  /** Marks items whose transitions, including pseudo-element ones, are suspended during a glide. */
49
49
  export const GLIDE_FREEZE_ATTRIBUTE = "data-glide-freeze";
50
- /** Converts a viewport rectangle into the container's scrolling coordinate space. */
50
+ /** Laid-out size of the border box, unaffected by any transform on the element or its ancestors. */
51
+ function layoutSize(element) {
52
+ const style = getComputedStyle(element);
53
+ const width = Number.parseFloat(style.width);
54
+ const height = Number.parseFloat(style.height);
55
+ if (!Number.isFinite(width) || !Number.isFinite(height)) {
56
+ return {
57
+ width: element.offsetWidth,
58
+ height: element.offsetHeight
59
+ };
60
+ }
61
+ if (style.boxSizing === "border-box") return {
62
+ width,
63
+ height
64
+ };
65
+ const sum = (...values) => values.reduce((total, value) => total + (Number.parseFloat(value) || 0), 0);
66
+ return {
67
+ width: width + sum(style.paddingLeft, style.paddingRight, style.borderLeftWidth, style.borderRightWidth),
68
+ height: height + sum(style.paddingTop, style.paddingBottom, style.borderTopWidth, style.borderBottomWidth)
69
+ };
70
+ }
71
+ /**
72
+ * Converts a viewport rectangle into the container's own scrolling coordinate space. A container
73
+ * still scaling in (a menu or listbox entrance) paints everything inside it smaller, so painted
74
+ * distances are divided by that scale to land where absolute positioning places the copy.
75
+ */
51
76
  function toContainerBox(container, rect) {
52
77
  const origin = container.getBoundingClientRect();
78
+ const layout = layoutSize(container);
79
+ const scaleX = layout.width > 0 && origin.width > 0 ? origin.width / layout.width : 1;
80
+ const scaleY = layout.height > 0 && origin.height > 0 ? origin.height / layout.height : 1;
53
81
  return {
54
- left: rect.left - origin.left - container.clientLeft + container.scrollLeft,
55
- top: rect.top - origin.top - container.clientTop + container.scrollTop,
56
- width: rect.width,
57
- height: rect.height
82
+ left: (rect.left - origin.left) / scaleX - container.clientLeft + container.scrollLeft,
83
+ top: (rect.top - origin.top) / scaleY - container.clientTop + container.scrollTop,
84
+ width: rect.width / scaleX,
85
+ height: rect.height / scaleY
58
86
  };
59
87
  }
60
- /** Translation an item's own running entrance adds, read from its computed `matrix()`. */
61
- function inFlightTranslation(item) {
62
- const values = /^matrix\(([^)]+)\)$/.exec(getComputedStyle(item).transform);
88
+ /** Scale and translation an item's own running entrance adds, read from its computed `matrix()`. */
89
+ function inFlightTransform(item) {
90
+ const style = getComputedStyle(item);
91
+ const values = /^matrix\(([^)]+)\)$/.exec(style.transform);
92
+ const [originX = 0, originY = 0] = style.transformOrigin.split(" ").map(Number.parseFloat);
63
93
  if (!values) return {
94
+ scaleX: 1,
95
+ scaleY: 1,
64
96
  x: 0,
65
- y: 0
97
+ y: 0,
98
+ originX,
99
+ originY
66
100
  };
67
101
  const parts = values[1].split(",").map(Number.parseFloat);
68
102
  return {
103
+ scaleX: parts[0] || 1,
104
+ scaleY: parts[3] || 1,
69
105
  x: parts[4] ?? 0,
70
- y: parts[5] ?? 0
106
+ y: parts[5] ?? 0,
107
+ originX,
108
+ originY
71
109
  };
72
110
  }
73
111
  /**
74
- * Measures the highlight: the item box, or its positioned `::after` bar for line variants.
75
- * An item still sliding into place (staggered Select options) is measured where it will
76
- * settle, so the highlight does not land offset and jump when the entrance ends.
112
+ * Measures the highlight in the container's coordinates: the item box, or its positioned `::after`
113
+ * bar for line variants. An item still entering (Select options settle from a slight scale) is
114
+ * measured where it will rest, so the highlight neither lands undersized nor jumps when it ends.
77
115
  */
78
- function highlightBox(item, surface) {
79
- const box = item.getBoundingClientRect();
80
- const offset = inFlightTranslation(item);
116
+ function highlightBox(container, item, surface) {
117
+ const painted = toContainerBox(container, item.getBoundingClientRect());
118
+ const motion = inFlightTransform(item);
119
+ // The entrance scales around the transform origin, so the resting corner sits that far back.
81
120
  const rect = {
82
- left: box.left - offset.x,
83
- top: box.top - offset.y,
84
- width: box.width,
85
- height: box.height
121
+ left: painted.left - motion.x - motion.originX * (1 - motion.scaleX),
122
+ top: painted.top - motion.y - motion.originY * (1 - motion.scaleY),
123
+ width: painted.width / motion.scaleX,
124
+ height: painted.height / motion.scaleY
86
125
  };
87
126
  if (surface === "background") return rect;
88
127
  const pseudo = getComputedStyle(item, "::after");
@@ -201,13 +240,13 @@ export function attachGlideIndicator(container, preset) {
201
240
  /** Starts, or redirects, the glide from the previous highlight to the new active item. */
202
241
  function glide(from, to) {
203
242
  const surface = preset.surface(container);
204
- const origin = overlay ? overlay.getBoundingClientRect() : highlightBox(from, surface);
243
+ const start = overlay ? toContainerBox(container, overlay.getBoundingClientRect()) : highlightBox(container, from, surface);
205
244
  settle();
206
245
  // The previous item drops its highlight instantly: the copy is what leaves it.
207
246
  freezeTransitions(from)();
208
247
  // Read the target's settled paint before any transition has a chance to start.
209
248
  const thawTarget = freezeTransitions(to);
210
- const destination = toContainerBox(container, highlightBox(to, surface));
249
+ const destination = highlightBox(container, to, surface);
211
250
  const paint = highlightPaint(to, surface);
212
251
  if (destination.width === 0 || destination.height === 0) {
213
252
  thawTarget();
@@ -227,7 +266,6 @@ export function attachGlideIndicator(container, preset) {
227
266
  zIndex: "-1",
228
267
  margin: "0"
229
268
  });
230
- const start = toContainerBox(container, origin);
231
269
  container.prepend(overlay);
232
270
  releaseTarget = hideTarget(to, surface, thawTarget);
233
271
  animation = animate(overlay, {