@mimi-ng/cli 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/README.md +14 -1
  2. package/dist/init/css.js +17 -0
  3. package/dist/init/index.js +7 -5
  4. package/dist/init/items.js +9 -0
  5. package/dist/registry.json +222 -1
  6. package/dist/templates/components/avatar/avatar.ts +1 -1
  7. package/dist/templates/components/avatar/avatar.variants.ts +4 -4
  8. package/dist/templates/components/badge/badge.ts +41 -6
  9. package/dist/templates/components/badge/badge.variants.ts +105 -11
  10. package/dist/templates/components/button/button.ts +74 -7
  11. package/dist/templates/components/button/button.variants.ts +145 -21
  12. package/dist/templates/components/card/card.ts +8 -5
  13. package/dist/templates/components/checkbox/checkbox.ts +3 -3
  14. package/dist/templates/components/confirm/confirm-texts.ts +46 -0
  15. package/dist/templates/components/confirm/confirm.ts +190 -0
  16. package/dist/templates/components/confirm/index.ts +2 -0
  17. package/dist/templates/components/context-menu/context-menu.ts +378 -0
  18. package/dist/templates/components/context-menu/index.ts +14 -0
  19. package/dist/templates/components/dialog/dialog-texts.ts +41 -0
  20. package/dist/templates/components/dialog/dialog.ts +701 -0
  21. package/dist/templates/components/dialog/index.ts +18 -0
  22. package/dist/templates/components/dropdown-menu/dropdown-menu.ts +252 -0
  23. package/dist/templates/components/dropdown-menu/index.ts +14 -0
  24. package/dist/templates/components/form-field/form-error.ts +1 -1
  25. package/dist/templates/components/form-field/form-field.ts +3 -2
  26. package/dist/templates/components/input/input.variants.ts +6 -4
  27. package/dist/templates/components/menu/index.ts +15 -0
  28. package/dist/templates/components/menu/menu.ts +656 -0
  29. package/dist/templates/components/pagination/index.ts +2 -0
  30. package/dist/templates/components/pagination/pagination-page-size.ts +74 -0
  31. package/dist/templates/components/pagination/pagination-texts.ts +69 -0
  32. package/dist/templates/components/pagination/pagination.ts +443 -0
  33. package/dist/templates/components/popover/index.ts +12 -0
  34. package/dist/templates/components/popover/popover.ts +399 -0
  35. package/dist/templates/components/select/index.ts +12 -0
  36. package/dist/templates/components/select/select-texts.ts +62 -0
  37. package/dist/templates/components/select/select.ts +1085 -0
  38. package/dist/templates/components/separator/separator.variants.ts +10 -7
  39. package/dist/templates/components/skeleton/skeleton.ts +4 -1
  40. package/dist/templates/components/switch/switch.ts +6 -6
  41. package/dist/templates/components/tabs/index.ts +2 -0
  42. package/dist/templates/components/tabs/tabs.ts +426 -0
  43. package/dist/templates/components/textarea/textarea.variants.ts +14 -11
  44. package/dist/templates/components/toast/index.ts +3 -0
  45. package/dist/templates/components/toast/toast-config.ts +45 -0
  46. package/dist/templates/components/toast/toast-texts.ts +46 -0
  47. package/dist/templates/components/toast/toast.ts +1000 -0
  48. package/dist/templates/components/toolbar/index.ts +1 -0
  49. package/dist/templates/components/toolbar/toolbar.ts +377 -0
  50. package/dist/templates/components/tooltip/index.ts +7 -0
  51. package/dist/templates/components/tooltip/tooltip.ts +407 -0
  52. package/dist/templates/theme/provider.ts +62 -6
  53. package/dist/templates/theme/theme-base.css +307 -9
  54. package/dist/templates/theme/types.ts +278 -4
  55. package/dist/templates/utils/cn.ts +15 -1
  56. package/dist/templates/utils/control-styles.ts +2 -2
  57. package/dist/templates/utils/overlay.ts +140 -0
  58. package/dist/ui/index.js +80 -8
  59. package/dist/ui/schema.json +44 -0
  60. package/dist/utils/write-files.js +156 -59
  61. package/package.json +1 -1
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * Preset tipado del tema (spec 6.3). Cada propiedad se convierte en una variable --mimi-*
3
- * de theme-base.css. Los derivados con color-mix (primary-hover, ring-soft, switch-off…)
4
- * no están aquí: se recalculan solos a partir de los colores.
3
+ * de theme-base.css. Los derivados con color-mix (primary-hover, ring-soft, switch-off y los
4
+ * <tono>-soft, <tono>-hover y <tono>-ring) no están aquí: se recalculan solos a partir de los
5
+ * colores.
5
6
  */
6
7
 
7
8
  /** Colores. `colors` se aplica en claro y `darkColors` en oscuro. */
@@ -25,9 +26,29 @@ export interface MimiColorTokens {
25
26
  border?: string;
26
27
  input?: string;
27
28
  inputBackground?: string;
29
+ /** Borde de un campo con el puntero encima (por defecto, mezcla de foreground e input). */
30
+ inputHover?: string;
28
31
  ring?: string;
29
32
  /** Fondo que oscurece la página detrás de paneles y diálogos. */
30
33
  overlay?: string;
34
+ success?: string;
35
+ successForeground?: string;
36
+ /** Texto sobre el fondo suave de success. */
37
+ successSoftForeground?: string;
38
+ warning?: string;
39
+ warningForeground?: string;
40
+ warningSoftForeground?: string;
41
+ info?: string;
42
+ infoForeground?: string;
43
+ infoSoftForeground?: string;
44
+ destructiveSoftForeground?: string;
45
+ /** Cuánto tono lleva cada fondo suave (-soft), en porcentaje: '12%'. */
46
+ softMix?: string;
47
+ tooltip?: string;
48
+ tooltipForeground?: string;
49
+ /** Fondo translúcido de las píldoras flotantes (mimi-toolbar, Pagination). */
50
+ glass?: string;
51
+ glassBorder?: string;
31
52
  }
32
53
 
33
54
  /** Sombras (`box-shadow` completo). `shadows` en claro y `darkShadows` en oscuro. */
@@ -41,12 +62,28 @@ export interface MimiShadowTokens {
41
62
  neutralHover?: string;
42
63
  /** Pulgar del Switch. */
43
64
  thumb?: string;
65
+ success?: string;
66
+ successHover?: string;
67
+ warning?: string;
68
+ warningHover?: string;
69
+ info?: string;
70
+ infoHover?: string;
71
+ /** Menús, diálogos, toasts y píldoras flotantes. */
72
+ popover?: string;
73
+ /** Glow (opción glow): van a --mimi-glow y --mimi-glow-<tono>, sin el prefijo shadow-. */
74
+ glow?: string;
75
+ glowPrimary?: string;
76
+ glowSuccess?: string;
77
+ glowWarning?: string;
78
+ glowInfo?: string;
79
+ glowDestructive?: string;
44
80
  }
45
81
 
46
82
  /** Radios fijos. Si no se definen, sm y card se calculan a partir de `radius`. */
47
83
  export interface MimiRadiusTokens {
48
84
  sm?: string;
49
85
  card?: string;
86
+ /** @deprecated Usa components.badge.radius (la misma variable). Sigue hasta la 1.0. */
50
87
  badge?: string;
51
88
  }
52
89
 
@@ -71,9 +108,41 @@ export interface MimiControlSizeTokens {
71
108
  heightLg?: string;
72
109
  }
73
110
 
111
+ /**
112
+ * Tokens compartidos por todos los controles (spec 13, nivel 2). Las alturas van a
113
+ * --mimi-control-height*; el resto, sin prefijo: focusRing → --mimi-focus-ring.
114
+ */
115
+ export interface MimiSharedTokens extends MimiControlSizeTokens {
116
+ /** Halo de foco en campos y botones (box-shadow). */
117
+ focusRing?: string;
118
+ /** Contorno de foco con teclado (outline). */
119
+ focusOutline?: string;
120
+ focusOffset?: string;
121
+ disabledOpacity?: string | number;
122
+ iconSize?: string;
123
+ iconSizeSm?: string;
124
+ }
125
+
126
+ /**
127
+ * Efectos que no son movimiento ni controles: desenfoques (y, más adelante, los de Mimi
128
+ * Effects). Iguales en claro y oscuro.
129
+ */
130
+ export interface MimiEffectTokens {
131
+ /** Desenfoque del fondo detrás de Dialog y Sheet. */
132
+ overlayBlur?: string;
133
+ /** Desenfoque de las píldoras translúcidas. */
134
+ glassBlur?: string;
135
+ }
136
+
74
137
  /** Tokens por componente. Cada componente los lee con la cascada de la spec 6.2. */
75
138
  export interface MimiControlTokens extends MimiControlSizeTokens {
76
139
  radius?: string;
140
+ /** Padding horizontal del tamaño default: --mimi-<prefijo>-px. */
141
+ px?: string;
142
+ /**
143
+ * @deprecated Nombre de la 0.1.0 (--mimi-<prefijo>-padding-x): usa `px`. Sigue funcionando
144
+ * hasta la 1.0 (spec 12).
145
+ */
77
146
  paddingX?: string;
78
147
  fontSize?: string;
79
148
  borderWidth?: string;
@@ -82,11 +151,19 @@ export interface MimiControlTokens extends MimiControlSizeTokens {
82
151
 
83
152
  /** paddingX y fontSize se aplican al tamaño default; sm y lg usan los valores del diseño. */
84
153
  export interface MimiButtonTokens extends MimiControlTokens {
154
+ /** Espacio entre ícono y texto del tamaño default. */
155
+ gap?: string;
156
+ /** Escala al presionar; si no, --mimi-press-scale. Con movimiento reducido vale 1. */
157
+ pressScale?: string | number;
85
158
  fontWeight?: string | number;
86
159
  letterSpacing?: string;
87
160
  }
88
161
 
89
162
  export interface MimiInputTokens extends MimiControlTokens {
163
+ /**
164
+ * @deprecated Es un color: cámbialo en el CSS con --mimi-input-placeholder (spec 6.2). Sigue
165
+ * funcionando hasta la 1.0.
166
+ */
90
167
  placeholderColor?: string;
91
168
  disabledOpacity?: string | number;
92
169
  }
@@ -95,16 +172,212 @@ export interface MimiCardTokens {
95
172
  radius?: string;
96
173
  borderWidth?: string;
97
174
  shadow?: string;
175
+ /** Padding exterior de las partes (1.5rem); los paddingHeader/Content/Footer lo detallan. */
176
+ padding?: string;
98
177
  paddingHeader?: string;
99
178
  paddingContent?: string;
100
179
  paddingFooter?: string;
101
180
  }
102
181
 
182
+ export interface MimiAvatarTokens {
183
+ size?: string;
184
+ sizeSm?: string;
185
+ sizeLg?: string;
186
+ radius?: string;
187
+ }
188
+
189
+ export interface MimiSwitchTokens {
190
+ /** Ancho de la pista. El pulgar se desplaza ancho − alto. */
191
+ width?: string;
192
+ /** Alto de la pista. El pulgar mide alto − 4px. */
193
+ height?: string;
194
+ }
195
+
196
+ export interface MimiCheckboxTokens {
197
+ size?: string;
198
+ radius?: string;
199
+ }
200
+
201
+ /** FormField (prefijo field). */
202
+ export interface MimiFieldTokens {
203
+ gap?: string;
204
+ labelSize?: string;
205
+ labelWeight?: string | number;
206
+ }
207
+
208
+ export interface MimiSeparatorTokens {
209
+ /** Grosor de la línea. */
210
+ size?: string;
211
+ }
212
+
213
+ export interface MimiSkeletonTokens {
214
+ radius?: string;
215
+ /** Duración del pulso: '1.6s'. */
216
+ duration?: string;
217
+ }
218
+
219
+ export interface MimiBadgeTokens {
220
+ height?: string;
221
+ px?: string;
222
+ fontSize?: string;
223
+ fontWeight?: string | number;
224
+ /** --mimi-badge-radius (el mismo que radii.badge de la 0.1.0). */
225
+ radius?: string;
226
+ }
227
+
228
+ /** Textarea: si no se definen, usa los de Input y después los compartidos. */
229
+ export interface MimiTextareaTokens {
230
+ radius?: string;
231
+ minHeight?: string;
232
+ lineHeight?: string;
233
+ py?: string;
234
+ }
235
+
236
+ /** Popover. Fondo, borde y sombra van en el CSS (`--mimi-popover-bg`, `-border`, `-shadow`). */
237
+ export interface MimiPopoverTokens {
238
+ radius?: string;
239
+ /** Padding del panel (16px). */
240
+ padding?: string;
241
+ /** Lado de la flecha (10px). */
242
+ arrow?: string;
243
+ }
244
+
245
+ /** Tooltip. Fondo y texto van en el CSS (`--mimi-tooltip-bg`, `--mimi-tooltip-fg`). */
246
+ export interface MimiTooltipTokens {
247
+ radius?: string;
248
+ fontSize?: string;
249
+ /** Lado de la flecha (8px). */
250
+ arrow?: string;
251
+ /** Retraso al abrir (300ms). `mimiTooltipDelay` lo cambia en un tooltip. */
252
+ delay?: string;
253
+ }
254
+
255
+ /**
256
+ * Dropdown Menu y Context Menu. Fondo, borde, sombra, hover, atajo y tono de peligro van en el CSS
257
+ * (`--mimi-menu-bg`, `-border`, `-shadow`, `-item-hover`, `-shortcut`, `-danger`, `-danger-hover`).
258
+ */
259
+ export interface MimiMenuTokens {
260
+ radius?: string;
261
+ /** Ancho del panel (240px). */
262
+ width?: string;
263
+ /** Alto de cada ítem (32px). */
264
+ itemHeight?: string;
265
+ itemRadius?: string;
266
+ }
267
+
268
+ /**
269
+ * Select. Fondos, borde, sombra y opción activa van en el CSS (`--mimi-select-trigger-bg`,
270
+ * `-border`, `-menu-bg`, `-menu-shadow`, `-option-hover`).
271
+ */
272
+ export interface MimiSelectTokens {
273
+ /** Radio del disparador y del panel. */
274
+ radius?: string;
275
+ /** Alto del disparador (el de los controles). */
276
+ height?: string;
277
+ /** Alto de cada opción (34px). */
278
+ optionHeight?: string;
279
+ optionRadius?: string;
280
+ }
281
+
282
+ /**
283
+ * Dialog. El fondo oscurecido, el fondo del panel y la sombra van en el CSS
284
+ * (`--mimi-dialog-overlay`, `-bg`, `-shadow`).
285
+ */
286
+ export interface MimiDialogTokens {
287
+ radius?: string;
288
+ /** Ancho del panel (420px). */
289
+ width?: string;
290
+ /** Relleno a los lados del encabezado, el cuerpo y el pie (24px). */
291
+ padding?: string;
292
+ /** Tamaño de la X (32px). */
293
+ closeSize?: string;
294
+ /** Desenfoque del fondo (el global `effects.overlayBlur`). */
295
+ overlayBlur?: string;
296
+ }
297
+
298
+ /**
299
+ * Confirm. Los colores del círculo del tono danger van en el CSS (`--mimi-confirm-icon-bg`,
300
+ * `-icon-fg`).
301
+ */
302
+ export interface MimiConfirmTokens {
303
+ /** Ancho del panel (380px). */
304
+ width?: string;
305
+ }
306
+
307
+ /**
308
+ * Toast. El fondo, el borde y la sombra van en el CSS (`--mimi-toast-bg`, `-border`, `-shadow`).
309
+ */
310
+ export interface MimiToastTokens {
311
+ radius?: string;
312
+ /** Ancho de la pila (340px). */
313
+ width?: string;
314
+ /** Separación de la pila con el borde de la pantalla (16px). */
315
+ offset?: string;
316
+ /** Cuánto asoma cada toast de atrás con la pila colapsada (12px). */
317
+ stackGap?: string;
318
+ }
319
+
320
+ /**
321
+ * Tabs. Los colores (pestañas, indicador y píldora) van en el CSS (`--mimi-tabs-fg`,
322
+ * `-active-fg`, `-indicator`, `-pill-bg`, `-pill-active`).
323
+ */
324
+ export interface MimiTabsTokens {
325
+ /** Alto de las pestañas subrayadas (44px). */
326
+ height?: string;
327
+ /** Curva del indicador (`cubic-bezier(.3, 1.35, .5, 1)`). */
328
+ spring?: string;
329
+ }
330
+
331
+ /**
332
+ * mimi-toolbar. Fondo, borde, sombra, disco activo, anillo y presionado van en el CSS
333
+ * (`--mimi-toolbar-bg`, `-border`, `-shadow`, `-active-bg`, `-active-fg`, `-ring`, `-pressed-bg`).
334
+ */
335
+ export interface MimiToolbarTokens {
336
+ /** Tamaño de cada botón (40px). */
337
+ itemSize?: string;
338
+ /** Desenfoque del fondo (el global `effects.glassBlur`). */
339
+ blur?: string;
340
+ }
341
+
342
+ /**
343
+ * Pagination. Fondo, borde, sombra flotante y error van en el CSS (`--mimi-pagination-bg`,
344
+ * `-border`, `-shadow`, `-error`).
345
+ */
346
+ export interface MimiPaginationTokens {
347
+ /** Tamaño de ← y → y alto del selector (el alto de control: sm 32px, md 40px). */
348
+ itemSize?: string;
349
+ /** Ancho del campo de página (sm 44px, md 52px). */
350
+ inputWidth?: string;
351
+ /** Desenfoque del fondo (el global `effects.glassBlur`). */
352
+ blur?: string;
353
+ }
354
+
355
+ /**
356
+ * Tokens por componente (spec 13). Solo los que no dependen del modo: los colores de los
357
+ * componentes (`--mimi-<prefijo>-bg`, `-fg`, `-border`…) se cambian en el CSS (spec 6.2).
358
+ */
103
359
  export interface MimiComponentTokens {
104
360
  button?: MimiButtonTokens;
105
361
  input?: MimiInputTokens;
106
362
  card?: MimiCardTokens;
107
- // select y dialog se agregan en la Fase 4
363
+ avatar?: MimiAvatarTokens;
364
+ switch?: MimiSwitchTokens;
365
+ checkbox?: MimiCheckboxTokens;
366
+ formField?: MimiFieldTokens;
367
+ separator?: MimiSeparatorTokens;
368
+ skeleton?: MimiSkeletonTokens;
369
+ badge?: MimiBadgeTokens;
370
+ textarea?: MimiTextareaTokens;
371
+ popover?: MimiPopoverTokens;
372
+ tooltip?: MimiTooltipTokens;
373
+ menu?: MimiMenuTokens;
374
+ select?: MimiSelectTokens;
375
+ dialog?: MimiDialogTokens;
376
+ confirm?: MimiConfirmTokens;
377
+ toast?: MimiToastTokens;
378
+ tabs?: MimiTabsTokens;
379
+ toolbar?: MimiToolbarTokens;
380
+ pagination?: MimiPaginationTokens;
108
381
  }
109
382
 
110
383
  export interface MimiThemePreset {
@@ -119,6 +392,7 @@ export interface MimiThemePreset {
119
392
  darkShadows?: MimiShadowTokens;
120
393
  fonts?: MimiFontTokens;
121
394
  motion?: MimiMotionTokens;
122
- controls?: MimiControlSizeTokens;
395
+ effects?: MimiEffectTokens;
396
+ controls?: MimiSharedTokens;
123
397
  components?: MimiComponentTokens;
124
398
  }
@@ -5,7 +5,7 @@ import { extendTailwindMerge } from 'tailwind-merge';
5
5
  * tailwind-merge solo reconoce tallas (sm, md, lg…) en las escalas de sombra y radio.
6
6
  * Sin registrar los tokens de Mimi, `shadow-card` se tomaría como color de sombra y
7
7
  * `rounded-card` no se fusionaría con otros `rounded-*`; lo mismo `animate-mimi-pulse` con
8
- * `animate-none`. Los colores no hace falta registrarlos: la escala de color acepta cualquier
8
+ * `animate-none` y `backdrop-blur-glass` con otros `backdrop-blur-*`. Los colores no hace falta registrarlos: la escala de color acepta cualquier
9
9
  * nombre.
10
10
  */
11
11
  const twMerge = extendTailwindMerge({
@@ -20,8 +20,22 @@ const twMerge = extendTailwindMerge({
20
20
  'neutral',
21
21
  'neutral-hover',
22
22
  'thumb',
23
+ 'success',
24
+ 'success-hover',
25
+ 'warning',
26
+ 'warning-hover',
27
+ 'info',
28
+ 'info-hover',
29
+ 'popover',
30
+ 'glow',
31
+ 'glow-primary',
32
+ 'glow-success',
33
+ 'glow-warning',
34
+ 'glow-info',
35
+ 'glow-destructive',
23
36
  ],
24
37
  radius: ['card', 'badge'],
38
+ blur: ['glass', 'overlay'],
25
39
  animate: ['mimi-pulse'],
26
40
  },
27
41
  classGroups: {
@@ -19,9 +19,9 @@ export const fieldFocusStyles =
19
19
  export const controlDisabledStyles =
20
20
  'disabled:cursor-not-allowed disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50';
21
21
 
22
- /** Inválido: borde destructive fijo y halo destructive-soft solo al enfocar. */
22
+ /** Inválido: borde destructive fijo y halo destructive-ring solo al enfocar. */
23
23
  export const controlInvalidStyles =
24
- 'aria-invalid:border-destructive aria-invalid:focus-visible:border-destructive aria-invalid:focus-visible:ring-destructive-soft';
24
+ 'aria-invalid:border-destructive aria-invalid:focus-visible:border-destructive aria-invalid:focus-visible:ring-destructive-ring';
25
25
 
26
26
  /** Transición y escala al hacer clic. Solo botones. */
27
27
  export const controlPressStyles = 'mimi-transition active:scale-(--mimi-press-scale)';
@@ -0,0 +1,140 @@
1
+ import type { ConnectedPosition } from '@angular/cdk/overlay';
2
+
3
+ /*
4
+ * Posición, flecha y animación compartidas por los overlays de Mimi (Popover, Tooltip…). Usa
5
+ * tipos de @angular/cdk/overlay: impórtalo siempre con su archivo concreto
6
+ * (`@/components/ui/utils/overlay`), nunca desde el índice de utils, que no lo reexporta para no
7
+ * arrastrar el CDK a quien no lo usa.
8
+ */
9
+
10
+ export type MimiOverlaySide = 'top' | 'right' | 'bottom' | 'left';
11
+ export type MimiOverlayAlign = 'start' | 'center' | 'end';
12
+
13
+ /** Distancia mínima de un overlay al borde de la pantalla (no está en el diseño: spec 13, plan D6). */
14
+ export const MIMI_VIEWPORT_MARGIN = 8;
15
+
16
+ const OPPOSITE: Record<MimiOverlaySide, MimiOverlaySide> = {
17
+ top: 'bottom',
18
+ bottom: 'top',
19
+ left: 'right',
20
+ right: 'left',
21
+ };
22
+
23
+ function connectedPosition(
24
+ side: MimiOverlaySide,
25
+ align: MimiOverlayAlign,
26
+ offset: number,
27
+ ): ConnectedPosition {
28
+ if (side === 'top' || side === 'bottom') {
29
+ return side === 'bottom'
30
+ ? { originX: align, originY: 'bottom', overlayX: align, overlayY: 'top', offsetY: offset }
31
+ : { originX: align, originY: 'top', overlayX: align, overlayY: 'bottom', offsetY: -offset };
32
+ }
33
+ const y = align === 'start' ? 'top' : align === 'end' ? 'bottom' : 'center';
34
+ return side === 'right'
35
+ ? { originX: 'end', originY: y, overlayX: 'start', overlayY: y, offsetX: offset }
36
+ : { originX: 'start', originY: y, overlayX: 'end', overlayY: y, offsetX: -offset };
37
+ }
38
+
39
+ /**
40
+ * Posiciones para el CDK, en orden de preferencia: el lado y la alineación pedidos, el lado
41
+ * opuesto y, por último, los otros dos lados centrados. `offset` es la separación con el trigger.
42
+ */
43
+ export function mimiConnectedPositions(
44
+ side: MimiOverlaySide,
45
+ align: MimiOverlayAlign,
46
+ offset: number,
47
+ ): ConnectedPosition[] {
48
+ const others: MimiOverlaySide[] =
49
+ side === 'top' || side === 'bottom' ? ['right', 'left'] : ['bottom', 'top'];
50
+ return [
51
+ connectedPosition(side, align, offset),
52
+ connectedPosition(OPPOSITE[side], align, offset),
53
+ ...others.map((other) => connectedPosition(other, 'center', offset)),
54
+ ];
55
+ }
56
+
57
+ /**
58
+ * Posiciones para abrir en un punto (el puntero, en Context Menu): la esquina superior izquierda
59
+ * del panel en el punto; si no cabe, se voltea a la izquierda del punto, hacia arriba o ambas.
60
+ */
61
+ export function mimiPointPositions(): ConnectedPosition[] {
62
+ return [
63
+ { originX: 'start', originY: 'top', overlayX: 'start', overlayY: 'top' },
64
+ { originX: 'start', originY: 'top', overlayX: 'end', overlayY: 'top' },
65
+ { originX: 'start', originY: 'top', overlayX: 'start', overlayY: 'bottom' },
66
+ { originX: 'start', originY: 'top', overlayX: 'end', overlayY: 'bottom' },
67
+ ];
68
+ }
69
+
70
+ /** Lado en el que quedó el overlay, según la posición que eligió el CDK. */
71
+ export function mimiPlacedSide({
72
+ originX,
73
+ originY,
74
+ overlayX,
75
+ overlayY,
76
+ }: ConnectedPosition): MimiOverlaySide {
77
+ if (originY === 'bottom' && overlayY === 'top') return 'bottom';
78
+ if (originY === 'top' && overlayY === 'bottom') return 'top';
79
+ return originX === 'end' && overlayX === 'start' ? 'right' : 'left';
80
+ }
81
+
82
+ /**
83
+ * Dónde va la flecha para que apunte al centro del trigger: distancia desde el borde del panel
84
+ * (izquierdo arriba/abajo, superior a los lados), dentro del panel. null si aún no hay medidas.
85
+ */
86
+ export function mimiArrowOffset(
87
+ trigger: DOMRect,
88
+ panel: DOMRect,
89
+ side: MimiOverlaySide,
90
+ ): string | null {
91
+ if (panel.width === 0 || panel.height === 0) return null;
92
+ const vertical = side === 'top' || side === 'bottom';
93
+ const offset = vertical
94
+ ? trigger.left + trigger.width / 2 - panel.left
95
+ : trigger.top + trigger.height / 2 - panel.top;
96
+ const size = vertical ? panel.width : panel.height;
97
+ return `${Math.min(Math.max(offset, 0), size)}px`;
98
+ }
99
+
100
+ /**
101
+ * Entrada y salida de los overlays, para el `styles` del componente. Valores de
102
+ * --mimi-transition (spec 6.6): 0.15s cubic-bezier(.2,.8,.2,1), escala desde --mimi-press-scale.
103
+ * Sin animación con movimiento reducido. El panel lleva `data-mimi-overlay-panel` y `data-state`.
104
+ */
105
+ export const MIMI_OVERLAY_ANIMATION = `
106
+ @keyframes mimi-overlay-in {
107
+ from {
108
+ opacity: 0;
109
+ scale: var(--mimi-press-scale);
110
+ }
111
+ }
112
+ @keyframes mimi-overlay-out {
113
+ to {
114
+ opacity: 0;
115
+ scale: var(--mimi-press-scale);
116
+ }
117
+ }
118
+ [data-mimi-overlay-panel][data-state='open'] {
119
+ animation: mimi-overlay-in 0.15s cubic-bezier(0.2, 0.8, 0.2, 1);
120
+ }
121
+ [data-mimi-overlay-panel][data-state='closed'] {
122
+ animation: mimi-overlay-out 0.15s cubic-bezier(0.2, 0.8, 0.2, 1) forwards;
123
+ }
124
+ @media (prefers-reduced-motion: reduce) {
125
+ [data-mimi-overlay-panel] {
126
+ animation: none !important;
127
+ }
128
+ }
129
+ `;
130
+
131
+ /**
132
+ * Se resuelve cuando terminan las animaciones del elemento (la de salida). Sin animaciones
133
+ * (movimiento reducido, o un entorno sin Web Animations), enseguida.
134
+ */
135
+ export function mimiWaitForExit(element: HTMLElement | undefined): Promise<void> {
136
+ const animations = element?.getAnimations?.() ?? [];
137
+ return Promise.allSettled(animations.map((animation) => animation.finished)).then(
138
+ () => undefined,
139
+ );
140
+ }