@mimi-ng/cli 0.2.0 → 0.4.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.
- package/README.md +35 -2
- package/dist/init/css.js +1 -1
- package/dist/init/index.js +25 -0
- package/dist/init/paths.js +20 -0
- package/dist/registry.json +385 -14
- package/dist/templates/blocks/login-01/index.ts +1 -0
- package/dist/templates/blocks/login-01/login-01.html +174 -0
- package/dist/templates/blocks/login-01/login-01.ts +105 -0
- package/dist/templates/blocks/login-02/index.ts +1 -0
- package/dist/templates/blocks/login-02/login-02.html +186 -0
- package/dist/templates/blocks/login-02/login-02.ts +92 -0
- package/dist/templates/blocks/otp-01/index.ts +1 -0
- package/dist/templates/blocks/otp-01/otp-01.html +166 -0
- package/dist/templates/blocks/otp-01/otp-01.ts +212 -0
- package/dist/templates/blocks/pricing-01/index.ts +1 -0
- package/dist/templates/blocks/pricing-01/pricing-01.html +173 -0
- package/dist/templates/blocks/pricing-01/pricing-01.ts +178 -0
- package/dist/templates/blocks/settings-01/index.ts +1 -0
- package/dist/templates/blocks/settings-01/settings-01.html +424 -0
- package/dist/templates/blocks/settings-01/settings-01.ts +382 -0
- package/dist/templates/blocks/signup-01/index.ts +1 -0
- package/dist/templates/blocks/signup-01/signup-01.html +332 -0
- package/dist/templates/blocks/signup-01/signup-01.ts +176 -0
- package/dist/templates/components/calendar/calendar-texts.ts +72 -0
- package/dist/templates/components/calendar/calendar.ts +588 -0
- package/dist/templates/components/calendar/dates.ts +181 -0
- package/dist/templates/components/calendar/index.ts +3 -0
- package/dist/templates/components/chip/chip-texts.ts +40 -0
- package/dist/templates/components/chip/chip.ts +116 -0
- package/dist/templates/components/chip/index.ts +2 -0
- package/dist/templates/components/color-picker/color-picker-texts.ts +57 -0
- package/dist/templates/components/color-picker/color-picker.ts +718 -0
- package/dist/templates/components/color-picker/color.ts +157 -0
- package/dist/templates/components/color-picker/index.ts +3 -0
- package/dist/templates/components/combobox/combobox-texts.ts +70 -0
- package/dist/templates/components/combobox/combobox.ts +798 -0
- package/dist/templates/components/combobox/index.ts +2 -0
- package/dist/templates/components/date-picker/date-picker.ts +240 -0
- package/dist/templates/components/date-picker/index.ts +3 -0
- package/dist/templates/components/date-range/date-range.ts +308 -0
- package/dist/templates/components/date-range/index.ts +1 -0
- package/dist/templates/components/file-upload/file-upload-texts.ts +77 -0
- package/dist/templates/components/file-upload/file-upload.ts +698 -0
- package/dist/templates/components/file-upload/index.ts +2 -0
- package/dist/templates/components/input-mask/index.ts +1 -0
- package/dist/templates/components/input-mask/input-mask.ts +319 -0
- package/dist/templates/components/input-otp/index.ts +1 -0
- package/dist/templates/components/input-otp/input-otp.ts +212 -0
- package/dist/templates/components/number-input/index.ts +2 -0
- package/dist/templates/components/number-input/number-input-texts.ts +46 -0
- package/dist/templates/components/number-input/number-input.ts +450 -0
- package/dist/templates/components/option-list/index.ts +11 -0
- package/dist/templates/components/option-list/option-list.ts +518 -0
- package/dist/templates/components/password/index.ts +2 -0
- package/dist/templates/components/password/password-texts.ts +68 -0
- package/dist/templates/components/password/password.ts +363 -0
- package/dist/templates/components/popover/popover.ts +12 -4
- package/dist/templates/components/progress/index.ts +1 -0
- package/dist/templates/components/progress/progress.ts +217 -0
- package/dist/templates/components/select/select.ts +3 -2
- package/dist/templates/components/slider/index.ts +2 -0
- package/dist/templates/components/slider/slider-texts.ts +26 -0
- package/dist/templates/components/slider/slider.ts +425 -0
- package/dist/templates/components/tabs/tabs.ts +88 -23
- package/dist/templates/components/tags-input/index.ts +2 -0
- package/dist/templates/components/tags-input/tags-input-texts.ts +44 -0
- package/dist/templates/components/tags-input/tags-input.ts +303 -0
- package/dist/templates/components/toggle-group/index.ts +1 -0
- package/dist/templates/components/toggle-group/toggle-group.ts +406 -0
- package/dist/templates/theme/provider.ts +13 -0
- package/dist/templates/theme/theme-base.css +103 -0
- package/dist/templates/theme/types.ts +216 -0
- package/dist/templates/utils/overlay.ts +17 -4
- package/dist/ui/index.js +232 -10
- package/dist/ui/schema.json +82 -2
- package/dist/utils/project.js +35 -0
- package/dist/utils/tailwind.js +80 -0
- package/dist/utils/write-files.js +49 -9
- package/package.json +1 -1
|
@@ -352,6 +352,209 @@ export interface MimiPaginationTokens {
|
|
|
352
352
|
blur?: string;
|
|
353
353
|
}
|
|
354
354
|
|
|
355
|
+
/**
|
|
356
|
+
* Toggle Group. Fondo, textos, indicador y su sombra van en el CSS (`--mimi-toggle-group-bg`,
|
|
357
|
+
* `-fg`, `-active-fg`, `-indicator`, `-indicator-shadow`).
|
|
358
|
+
*/
|
|
359
|
+
export interface MimiToggleGroupTokens {
|
|
360
|
+
/** Radio de cada ítem y del indicador (el global `radius`); el del grupo es radio + relleno. */
|
|
361
|
+
radius?: string;
|
|
362
|
+
/** Relleno del grupo (3px). */
|
|
363
|
+
padding?: string;
|
|
364
|
+
/** Separación entre ítems (2px). */
|
|
365
|
+
gap?: string;
|
|
366
|
+
/** Alto de cada ítem (32px). */
|
|
367
|
+
itemHeight?: string;
|
|
368
|
+
/** Relleno horizontal de cada ítem (12px). */
|
|
369
|
+
itemPx?: string;
|
|
370
|
+
/** Ancho mínimo de un ítem (el de uno solo con ícono, 44px). */
|
|
371
|
+
iconWidth?: string;
|
|
372
|
+
/** Tamaño de texto (13.5px). */
|
|
373
|
+
fontSize?: string;
|
|
374
|
+
/** Curva del indicador (`cubic-bezier(.3, 1.35, .5, 1)`, la de Tabs). */
|
|
375
|
+
spring?: string;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Progress. La pista y la parte llena van en el CSS (`--mimi-progress-track`, `-fill`).
|
|
380
|
+
*/
|
|
381
|
+
export interface MimiProgressTokens {
|
|
382
|
+
/** Grosor de la barra (8px). */
|
|
383
|
+
height?: string;
|
|
384
|
+
/** Tamaño del círculo `default` (76px). */
|
|
385
|
+
circleSize?: string;
|
|
386
|
+
/** Grosor del trazo del círculo `default`, en unidades del viewBox de 36 (3). */
|
|
387
|
+
circleWidth?: string | number;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Number Input. El fondo del botón en hover va en el CSS (`--mimi-number-button-hover`); el
|
|
392
|
+
* borde, el fondo y los anillos son los de Input (globales).
|
|
393
|
+
*/
|
|
394
|
+
export interface MimiNumberInputTokens {
|
|
395
|
+
/** Altura (el alto de control). */
|
|
396
|
+
height?: string;
|
|
397
|
+
/** Radio de la caja (el global `radius`). */
|
|
398
|
+
radius?: string;
|
|
399
|
+
/** Relleno horizontal de la caja (4px). */
|
|
400
|
+
px?: string;
|
|
401
|
+
/** Tamaño de los botones − y + (30px). */
|
|
402
|
+
buttonSize?: string;
|
|
403
|
+
/** Radio de los botones (el global `radius-sm`). */
|
|
404
|
+
buttonRadius?: string;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Password. El hover del botón y la pista del medidor van en el CSS
|
|
409
|
+
* (`--mimi-password-toggle-hover`, `-meter-track`); los colores de los niveles son los globales
|
|
410
|
+
* `--mimi-strength-*`.
|
|
411
|
+
*/
|
|
412
|
+
export interface MimiPasswordTokens {
|
|
413
|
+
/** Botón de mostrar u ocultar (32px). */
|
|
414
|
+
toggleSize?: string;
|
|
415
|
+
/** Radio del botón (el global `radius-sm`). */
|
|
416
|
+
toggleRadius?: string;
|
|
417
|
+
/** Alto de cada barra del medidor (6px). */
|
|
418
|
+
meterHeight?: string;
|
|
419
|
+
/** Separación entre barras (4px). */
|
|
420
|
+
meterGap?: string;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Input Mask. El color del marcador y el fondo del prefijo van en el CSS
|
|
425
|
+
* (`--mimi-mask-placeholder`, `-prefix-bg`); la caja usa los tokens de Input.
|
|
426
|
+
*/
|
|
427
|
+
export interface MimiInputMaskTokens {
|
|
428
|
+
/** Separación entre el prefijo, el ícono y el texto (8px). */
|
|
429
|
+
gap?: string;
|
|
430
|
+
/** Alto del prefijo (26px). */
|
|
431
|
+
prefixHeight?: string;
|
|
432
|
+
/** Radio del prefijo (el global `radius-sm`). */
|
|
433
|
+
prefixRadius?: string;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Input OTP. Fondo, borde, separador y cursor van en el CSS (`--mimi-otp-bg`, `-border`,
|
|
438
|
+
* `-separator`, `-caret`).
|
|
439
|
+
*/
|
|
440
|
+
export interface MimiInputOtpTokens {
|
|
441
|
+
/** Ancho de la casilla (46px). */
|
|
442
|
+
width?: string;
|
|
443
|
+
/** Alto de la casilla (54px). */
|
|
444
|
+
height?: string;
|
|
445
|
+
/** Separación entre casillas (8px). */
|
|
446
|
+
gap?: string;
|
|
447
|
+
/** Radio (el global `radius`). */
|
|
448
|
+
radius?: string;
|
|
449
|
+
/** Tamaño del carácter (22px). */
|
|
450
|
+
fontSize?: string;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/** Chip (Tags Input y Combobox). Los colores van en el CSS (`--mimi-chip-bg`, `-fg`). */
|
|
454
|
+
export interface MimiChipTokens {
|
|
455
|
+
/** Alto (26px). */
|
|
456
|
+
height?: string;
|
|
457
|
+
/** Radio (el global `badge-radius`). */
|
|
458
|
+
radius?: string;
|
|
459
|
+
/** Tamaño de texto (13px). */
|
|
460
|
+
fontSize?: string;
|
|
461
|
+
/** Botón de quitar (18px). */
|
|
462
|
+
removeSize?: string;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/** Tags Input. El borde con el límite alcanzado va en el CSS (`--mimi-tags-full-border`). */
|
|
466
|
+
export interface MimiTagsInputTokens {
|
|
467
|
+
/** Separación entre chips (6px). */
|
|
468
|
+
gap?: string;
|
|
469
|
+
/** Relleno de la caja (`5px 10px 5px 6px`). */
|
|
470
|
+
padding?: string;
|
|
471
|
+
/** Ancho mínimo del campo de texto (120px). */
|
|
472
|
+
inputMinWidth?: string;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Slider. Pista, relleno, pulgar y su sombra van en el CSS (`--mimi-slider-track`, `-fill`,
|
|
477
|
+
* `-thumb`, `-thumb-shadow`); la burbuja usa los colores de Tooltip.
|
|
478
|
+
*/
|
|
479
|
+
export interface MimiSliderTokens {
|
|
480
|
+
/** Grosor de la pista (6px). */
|
|
481
|
+
height?: string;
|
|
482
|
+
/** Tamaño del pulgar (20px). */
|
|
483
|
+
thumbSize?: string;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Combobox. El fondo y la sombra del panel y la opción activa van en el CSS
|
|
488
|
+
* (`--mimi-combobox-menu-bg`, `-menu-shadow`, `-option-hover`).
|
|
489
|
+
*/
|
|
490
|
+
export interface MimiComboboxTokens {
|
|
491
|
+
/** Alto del campo (el global `control-height`). */
|
|
492
|
+
height?: string;
|
|
493
|
+
/** Radio del campo y del panel (el global `radius`). */
|
|
494
|
+
radius?: string;
|
|
495
|
+
/** Alto máximo del panel (228px). */
|
|
496
|
+
menuMaxHeight?: string;
|
|
497
|
+
/** Alto mínimo de cada opción (34px). */
|
|
498
|
+
optionHeight?: string;
|
|
499
|
+
/** Radio de cada opción (el global `radius-sm`). */
|
|
500
|
+
optionRadius?: string;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Calendario, Date Picker y Date Range (una tabla, decidido por defecto en la G2.9). Los colores
|
|
505
|
+
* van en el CSS (`--mimi-date-cell-hover`, `-selected-bg`, `-selected-fg`, `-range`,
|
|
506
|
+
* `-today-ring`, `-menu-bg`, `-menu-shadow`).
|
|
507
|
+
*/
|
|
508
|
+
export interface MimiDateTokens {
|
|
509
|
+
/** Tamaño de cada día (36px). */
|
|
510
|
+
cellSize?: string;
|
|
511
|
+
/** Radio de cada día (el global `radius-sm`). */
|
|
512
|
+
cellRadius?: string;
|
|
513
|
+
/** Relleno del panel (12px). */
|
|
514
|
+
padding?: string;
|
|
515
|
+
/** Ancho de la columna de atajos del rango (130px). */
|
|
516
|
+
presetsWidth?: string;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* File Upload. Fondos y bordes de la zona van en el CSS (`--mimi-upload-bg`, `-border`,
|
|
521
|
+
* `-active-bg`, `-active-border`).
|
|
522
|
+
*/
|
|
523
|
+
export interface MimiFileUploadTokens {
|
|
524
|
+
/** Radio de la zona (el global `radius-card`). */
|
|
525
|
+
radius?: string;
|
|
526
|
+
/** Alto mínimo de la zona (176px). */
|
|
527
|
+
height?: string;
|
|
528
|
+
/** Círculo del ícono (44px). */
|
|
529
|
+
iconSize?: string;
|
|
530
|
+
/** Radio de cada fila (el global `radius`). */
|
|
531
|
+
itemRadius?: string;
|
|
532
|
+
/** Miniatura de cada fila (44px). */
|
|
533
|
+
thumbSize?: string;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* Color Picker. Fondo, sombra, el borde y la sombra de los pulgares y el borde de las muestras
|
|
538
|
+
* van en el CSS (`--mimi-color-picker-bg`, `-shadow`, `-thumb-border`, `-thumb-shadow`,
|
|
539
|
+
* `-swatch-border`).
|
|
540
|
+
*/
|
|
541
|
+
export interface MimiColorPickerTokens {
|
|
542
|
+
/** Radio del panel (el global `radius`). */
|
|
543
|
+
radius?: string;
|
|
544
|
+
/** Ancho del panel (280px). */
|
|
545
|
+
width?: string;
|
|
546
|
+
/** Relleno y separación (12px). */
|
|
547
|
+
padding?: string;
|
|
548
|
+
/** Alto del área de saturación (150px). */
|
|
549
|
+
areaHeight?: string;
|
|
550
|
+
/** Radio del área (el global `radius-sm`). */
|
|
551
|
+
areaRadius?: string;
|
|
552
|
+
/** Alto de la barra de tono (12px). */
|
|
553
|
+
hueHeight?: string;
|
|
554
|
+
/** Separación de las muestras (6px). */
|
|
555
|
+
swatchGap?: string;
|
|
556
|
+
}
|
|
557
|
+
|
|
355
558
|
/**
|
|
356
559
|
* Tokens por componente (spec 13). Solo los que no dependen del modo: los colores de los
|
|
357
560
|
* componentes (`--mimi-<prefijo>-bg`, `-fg`, `-border`…) se cambian en el CSS (spec 6.2).
|
|
@@ -378,6 +581,19 @@ export interface MimiComponentTokens {
|
|
|
378
581
|
tabs?: MimiTabsTokens;
|
|
379
582
|
toolbar?: MimiToolbarTokens;
|
|
380
583
|
pagination?: MimiPaginationTokens;
|
|
584
|
+
toggleGroup?: MimiToggleGroupTokens;
|
|
585
|
+
progress?: MimiProgressTokens;
|
|
586
|
+
numberInput?: MimiNumberInputTokens;
|
|
587
|
+
password?: MimiPasswordTokens;
|
|
588
|
+
inputMask?: MimiInputMaskTokens;
|
|
589
|
+
inputOtp?: MimiInputOtpTokens;
|
|
590
|
+
chip?: MimiChipTokens;
|
|
591
|
+
tagsInput?: MimiTagsInputTokens;
|
|
592
|
+
slider?: MimiSliderTokens;
|
|
593
|
+
combobox?: MimiComboboxTokens;
|
|
594
|
+
date?: MimiDateTokens;
|
|
595
|
+
fileUpload?: MimiFileUploadTokens;
|
|
596
|
+
colorPicker?: MimiColorPickerTokens;
|
|
381
597
|
}
|
|
382
598
|
|
|
383
599
|
export interface MimiThemePreset {
|
|
@@ -36,9 +36,21 @@ function connectedPosition(
|
|
|
36
36
|
: { originX: 'start', originY: y, overlayX: 'end', overlayY: y, offsetX: -offset };
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
/** Las otras alineaciones, en orden de preferencia (la opuesta antes que el centro). */
|
|
40
|
+
const FALLBACK_ALIGNS: Record<MimiOverlayAlign, MimiOverlayAlign[]> = {
|
|
41
|
+
start: ['end', 'center'],
|
|
42
|
+
end: ['start', 'center'],
|
|
43
|
+
center: ['start', 'end'],
|
|
44
|
+
};
|
|
45
|
+
|
|
39
46
|
/**
|
|
40
|
-
* Posiciones para el CDK, en orden de preferencia: el lado
|
|
41
|
-
* opuesto y, por último, los otros dos lados
|
|
47
|
+
* Posiciones para el CDK, en orden de preferencia: el lado pedido con la alineación pedida y
|
|
48
|
+
* después con las otras dos; el lado opuesto, igual; y, por último, los otros dos lados
|
|
49
|
+
* centrados. `offset` es la separación con el trigger.
|
|
50
|
+
*
|
|
51
|
+
* Las otras alineaciones del mismo lado (5.17a) evitan que en una pantalla chica el panel se
|
|
52
|
+
* salga por un costado: en 360px, un menú de 240px alineado al inicio de un trigger cercano al
|
|
53
|
+
* borde derecho no cabía en ninguna de las cuatro posiciones de antes y el CDK lo dejaba fuera.
|
|
42
54
|
*/
|
|
43
55
|
export function mimiConnectedPositions(
|
|
44
56
|
side: MimiOverlaySide,
|
|
@@ -47,9 +59,10 @@ export function mimiConnectedPositions(
|
|
|
47
59
|
): ConnectedPosition[] {
|
|
48
60
|
const others: MimiOverlaySide[] =
|
|
49
61
|
side === 'top' || side === 'bottom' ? ['right', 'left'] : ['bottom', 'top'];
|
|
62
|
+
const aligns = [align, ...FALLBACK_ALIGNS[align]];
|
|
50
63
|
return [
|
|
51
|
-
connectedPosition(side,
|
|
52
|
-
connectedPosition(OPPOSITE[side],
|
|
64
|
+
...aligns.map((a) => connectedPosition(side, a, offset)),
|
|
65
|
+
...aligns.map((a) => connectedPosition(OPPOSITE[side], a, offset)),
|
|
53
66
|
...others.map((other) => connectedPosition(other, 'center', offset)),
|
|
54
67
|
];
|
|
55
68
|
}
|
package/dist/ui/index.js
CHANGED
|
@@ -9,12 +9,17 @@ const items_1 = require("../init/items");
|
|
|
9
9
|
const paths_1 = require("../init/paths");
|
|
10
10
|
const registry_1 = require("../registry");
|
|
11
11
|
const project_1 = require("../utils/project");
|
|
12
|
+
const tailwind_1 = require("../utils/tailwind");
|
|
12
13
|
const write_files_1 = require("../utils/write-files");
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
.filter(([, item]) => item.type === 'component')
|
|
14
|
+
const namesOf = (type) => Object.entries(registry_1.registry.items)
|
|
15
|
+
.filter(([, item]) => item.type === type)
|
|
16
16
|
.map(([name]) => name)
|
|
17
17
|
.sort();
|
|
18
|
+
/**
|
|
19
|
+
* Lo que se puede agregar: los ítems `component` del registro y, después, los `block` (B.1-c;
|
|
20
|
+
* el mismo orden que el x-prompt).
|
|
21
|
+
*/
|
|
22
|
+
exports.AVAILABLE = [...namesOf('component'), ...namesOf('block')];
|
|
18
23
|
/**
|
|
19
24
|
* `ng g mimi button input form-field`: copia los componentes y sus dependencias del registro a la
|
|
20
25
|
* carpeta de mimi.json. Sin nombres, pregunta (x-prompt de selección múltiple).
|
|
@@ -34,12 +39,14 @@ function ui(options) {
|
|
|
34
39
|
items: [...plan.items, ...plan.implicit],
|
|
35
40
|
implicit: plan.implicit,
|
|
36
41
|
componentsDir: plan.componentsDir,
|
|
42
|
+
blocksDir: plan.blocksDir,
|
|
37
43
|
overwrite: options.overwrite ?? false,
|
|
38
44
|
version: registry_1.registry.version,
|
|
39
45
|
report,
|
|
40
46
|
}),
|
|
41
47
|
updateMimiConfig(plan, report),
|
|
42
48
|
updateGlobalCss(plan),
|
|
49
|
+
addBlocksSource(plan),
|
|
43
50
|
...Object.entries(plan.newPackages).map(([name, version]) => (0, utility_1.addDependency)(name, version, {
|
|
44
51
|
type: utility_1.DependencyType.Default,
|
|
45
52
|
existing: utility_1.ExistingBehavior.Skip,
|
|
@@ -56,6 +63,7 @@ function inspect(tree, options) {
|
|
|
56
63
|
throw new schematics_1.SchematicsException('Mimi no está configurado en este proyecto (falta mimi.json). Ejecuta primero:\n' +
|
|
57
64
|
' ng add @mimi-ng/cli');
|
|
58
65
|
}
|
|
66
|
+
(0, project_1.assertAliases)(config);
|
|
59
67
|
const requested = [...new Set((options.components ?? []).map((name) => name.trim()))].filter(Boolean);
|
|
60
68
|
if (requested.length === 0) {
|
|
61
69
|
throw new schematics_1.SchematicsException(`Indica qué componentes agregar, por ejemplo: ng g mimi button\nDisponibles: ${exports.AVAILABLE.join(', ')}.`);
|
|
@@ -64,7 +72,7 @@ function inspect(tree, options) {
|
|
|
64
72
|
if (unknown.length > 0) {
|
|
65
73
|
const lines = unknown.map((name) => {
|
|
66
74
|
const suggestion = closest(name);
|
|
67
|
-
return `"${name}" no es un componente de Mimi.${suggestion ? ` ¿Quisiste decir ${suggestion}?` : ''}`;
|
|
75
|
+
return `"${name}" no es un componente ni un Block de Mimi.${suggestion ? ` ¿Quisiste decir ${suggestion}?` : ''}`;
|
|
68
76
|
});
|
|
69
77
|
throw new schematics_1.SchematicsException(`${lines.join('\n')}\nDisponibles: ${exports.AVAILABLE.join(', ')}.`);
|
|
70
78
|
}
|
|
@@ -103,7 +111,132 @@ function inspect(tree, options) {
|
|
|
103
111
|
}
|
|
104
112
|
const implicit = [...new Set(items_1.INIT_ITEMS.flatMap((item) => (0, registry_1.resolveItems)(item)))].filter((item) => !items.includes(item));
|
|
105
113
|
const componentsDir = (0, paths_1.normalizePath)(config.aliases?.components ?? 'src/app/components/ui');
|
|
106
|
-
|
|
114
|
+
const blocksDir = (0, paths_1.normalizePath)(config.aliases?.blocks ?? (0, project_1.defaultBlocksDir)(componentsDir));
|
|
115
|
+
const outdated = outdatedPeers(tree, items, installed, newPackages);
|
|
116
|
+
const updatable = PEERS_WITH_MINIMUM.filter((name) => installed[name] || newPackages[name]);
|
|
117
|
+
const missingExclusion = mimiExclusion(tree, config, installed);
|
|
118
|
+
return {
|
|
119
|
+
componentsDir,
|
|
120
|
+
blocksDir,
|
|
121
|
+
config,
|
|
122
|
+
items,
|
|
123
|
+
implicit,
|
|
124
|
+
newPackages,
|
|
125
|
+
outdated,
|
|
126
|
+
updatable,
|
|
127
|
+
missingExclusion,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Sin `@source not` hacia `.mimi/`, Tailwind escanea las copias originales (van en git) y genera
|
|
132
|
+
* sus clases aunque el usuario ya las haya cambiado en sus componentes. Proyectos de la 0.3.0 o
|
|
133
|
+
* anteriores no la tienen: se avisa para que no tengan que volver a ejecutar ng add. Con
|
|
134
|
+
* Tailwind 4.0 no se avisa aquí (no la entiende; init ya lo explicó).
|
|
135
|
+
*/
|
|
136
|
+
function mimiExclusion(tree, config, installed) {
|
|
137
|
+
const cssPath = config.tailwind?.css;
|
|
138
|
+
if (!cssPath || !tree.exists(cssPath))
|
|
139
|
+
return null;
|
|
140
|
+
if (!(0, tailwind_1.tailwindSupportsSourceNot)(tree, installed['tailwindcss']))
|
|
141
|
+
return null;
|
|
142
|
+
if (!(0, tailwind_1.missingMimiExclusion)(tree.readText(cssPath), cssPath))
|
|
143
|
+
return null;
|
|
144
|
+
return { cssPath, line: (0, tailwind_1.mimiSourceNotLine)(cssPath) };
|
|
145
|
+
}
|
|
146
|
+
// ── Versiones mínimas de cdk y aria (M.4) ─────────────────────────────────────────────
|
|
147
|
+
/**
|
|
148
|
+
* Paquetes cuya versión mínima se vigila. La CLI no los cambia si el proyecto ya los tiene (se
|
|
149
|
+
* actualizan con `ng update`, junto con el resto de Angular), pero avisa si quedan por debajo de
|
|
150
|
+
* lo que pide el registro: con aria y cdk 22.2.3, Combobox y Select lanzan NG0950 (spec 4,
|
|
151
|
+
* «#33974»).
|
|
152
|
+
*/
|
|
153
|
+
const PEERS_WITH_MINIMUM = ['@angular/cdk', '@angular/aria'];
|
|
154
|
+
const RANGE = /^\s*(\^|~|>=|=)?\s*v?(\d+)\.(\d+)\.(\d+)\s*$/;
|
|
155
|
+
function parseRange(range) {
|
|
156
|
+
const match = RANGE.exec(range);
|
|
157
|
+
if (!match)
|
|
158
|
+
return null;
|
|
159
|
+
return { op: match[1] ?? '=', version: [+match[2], +match[3], +match[4]] };
|
|
160
|
+
}
|
|
161
|
+
function compare(a, b) {
|
|
162
|
+
return a[0] - b[0] || a[1] - b[1] || a[2] - b[2];
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Si un rango puede instalar `required` o una posterior. Lo que no se entiende (`*`, `latest`,
|
|
166
|
+
* `22.2.x`, `workspace:`…) cuenta como que sí: el aviso no da falsas alarmas.
|
|
167
|
+
*/
|
|
168
|
+
function canReach(range, required) {
|
|
169
|
+
const parsed = parseRange(range);
|
|
170
|
+
if (!parsed)
|
|
171
|
+
return true;
|
|
172
|
+
if (parsed.op === '>=' || compare(parsed.version, required) >= 0)
|
|
173
|
+
return true;
|
|
174
|
+
// Por debajo del mínimo: llega solo si el tope del rango (exclusivo) queda por encima.
|
|
175
|
+
const [major, minor, patch] = parsed.version;
|
|
176
|
+
const upper = parsed.op === '^'
|
|
177
|
+
? major > 0
|
|
178
|
+
? [major + 1, 0, 0]
|
|
179
|
+
: minor > 0
|
|
180
|
+
? [0, minor + 1, 0]
|
|
181
|
+
: [0, 0, patch + 1]
|
|
182
|
+
: parsed.op === '~'
|
|
183
|
+
? [major, minor + 1, 0]
|
|
184
|
+
: null;
|
|
185
|
+
return upper !== null && compare(required, upper) < 0;
|
|
186
|
+
}
|
|
187
|
+
/** El mínimo que piden los ítems para cada paquete vigilado (aria implica el mismo cdk). */
|
|
188
|
+
function requiredMinimums(items) {
|
|
189
|
+
const minimums = new Map();
|
|
190
|
+
for (const item of items) {
|
|
191
|
+
const { dependencies = {}, peerDependencies = {} } = registry_1.registry.items[item];
|
|
192
|
+
for (const [name, range] of Object.entries({ ...dependencies, ...peerDependencies })) {
|
|
193
|
+
if (!PEERS_WITH_MINIMUM.includes(name))
|
|
194
|
+
continue;
|
|
195
|
+
const version = parseRange(range)?.version;
|
|
196
|
+
if (!version)
|
|
197
|
+
continue;
|
|
198
|
+
const names = name === '@angular/aria' ? [name, '@angular/cdk'] : [name];
|
|
199
|
+
for (const target of names) {
|
|
200
|
+
const known = minimums.get(target);
|
|
201
|
+
if (!known || compare(version, known) > 0)
|
|
202
|
+
minimums.set(target, version);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return minimums;
|
|
207
|
+
}
|
|
208
|
+
function outdatedPeers(tree, items, installed, newPackages) {
|
|
209
|
+
const outdated = [];
|
|
210
|
+
for (const [name, required] of requiredMinimums(items)) {
|
|
211
|
+
const declared = installed[name] ?? newPackages[name];
|
|
212
|
+
if (!declared)
|
|
213
|
+
continue;
|
|
214
|
+
// Si ya está instalado, manda la versión de node_modules (la que corre de verdad).
|
|
215
|
+
const onDisk = installed[name] ? installedVersion(tree, name) : null;
|
|
216
|
+
const version = onDisk ? parseRange(onDisk)?.version : null;
|
|
217
|
+
const ok = version ? compare(version, required) >= 0 : canReach(declared, required);
|
|
218
|
+
if (!ok) {
|
|
219
|
+
outdated.push({
|
|
220
|
+
name,
|
|
221
|
+
current: onDisk ?? declared,
|
|
222
|
+
source: onDisk ? 'installed' : installed[name] ? 'declared' : 'added',
|
|
223
|
+
required: required.join('.'),
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return outdated.sort((a, b) => a.name.localeCompare(b.name));
|
|
228
|
+
}
|
|
229
|
+
const SOURCE_TEXT = {
|
|
230
|
+
installed: (current) => `${current} instalada`,
|
|
231
|
+
declared: (current) => `"${current}" en package.json`,
|
|
232
|
+
added: (current) => `"${current}", que se agrega ahora`,
|
|
233
|
+
};
|
|
234
|
+
function installedVersion(tree, name) {
|
|
235
|
+
const file = tree.read(`node_modules/${name}/package.json`);
|
|
236
|
+
if (!file)
|
|
237
|
+
return null;
|
|
238
|
+
const { version } = JSON.parse(file.toString('utf-8'));
|
|
239
|
+
return version ?? null;
|
|
107
240
|
}
|
|
108
241
|
/**
|
|
109
242
|
* @angular/aria pide como peer la versión exacta de @angular/cdk (22.1.7 → cdk 22.1.7). Si el cdk
|
|
@@ -155,16 +288,25 @@ function updateMimiConfig(plan, report) {
|
|
|
155
288
|
const skipped = new Set(report.skipped);
|
|
156
289
|
const blocked = new Set(report.blocked.map((b) => b.item));
|
|
157
290
|
const components = { ...plan.config.components };
|
|
291
|
+
const blocks = { ...plan.config.blocks };
|
|
158
292
|
for (const item of plan.items) {
|
|
159
|
-
|
|
293
|
+
const { type, files } = registry_1.registry.items[item];
|
|
294
|
+
if (type !== 'component' && type !== 'block')
|
|
160
295
|
continue;
|
|
161
296
|
if (blocked.has(item))
|
|
162
297
|
continue;
|
|
163
|
-
if (
|
|
298
|
+
if (files.some((file) => skipped.has(file)))
|
|
164
299
|
continue;
|
|
165
|
-
components[item] = { version: registry_1.registry.version };
|
|
300
|
+
(type === 'block' ? blocks : components)[item] = { version: registry_1.registry.version };
|
|
166
301
|
}
|
|
167
302
|
const next = { ...plan.config, components };
|
|
303
|
+
if (Object.keys(blocks).length > 0) {
|
|
304
|
+
next.blocks = blocks;
|
|
305
|
+
// Proyectos de la 0.3.0 o anteriores: la carpeta queda escrita la primera vez (B.1-a).
|
|
306
|
+
if (!plan.config.aliases?.blocks) {
|
|
307
|
+
next.aliases = { ...plan.config.aliases, blocks: plan.blocksDir };
|
|
308
|
+
}
|
|
309
|
+
}
|
|
168
310
|
const text = `${JSON.stringify(next, null, 2)}\n`;
|
|
169
311
|
if (tree.readText('mimi.json') !== text)
|
|
170
312
|
tree.overwrite('mimi.json', text);
|
|
@@ -202,11 +344,58 @@ function updateGlobalCss(plan) {
|
|
|
202
344
|
context.logger.info(` ${line}`);
|
|
203
345
|
};
|
|
204
346
|
}
|
|
347
|
+
/**
|
|
348
|
+
* G1: con `@import "tailwindcss" source(none)`, Tailwind no escanea la carpeta de los Blocks y
|
|
349
|
+
* la pantalla queda sin sus estilos. Si se copia un Block, se agrega su `@source` (una vez).
|
|
350
|
+
*/
|
|
351
|
+
function addBlocksSource(plan) {
|
|
352
|
+
return (tree, context) => {
|
|
353
|
+
if (!plan.items.some((item) => registry_1.registry.items[item].type === 'block'))
|
|
354
|
+
return;
|
|
355
|
+
const cssPath = plan.config.tailwind?.css;
|
|
356
|
+
if (!cssPath || !tree.exists(cssPath))
|
|
357
|
+
return;
|
|
358
|
+
const result = (0, tailwind_1.withBlocksSource)(tree.readText(cssPath), cssPath, plan.componentsDir, plan.blocksDir);
|
|
359
|
+
if (!result)
|
|
360
|
+
return;
|
|
361
|
+
tree.overwrite(cssPath, result.css);
|
|
362
|
+
context.logger.info(`Agregado a ${cssPath} (tu @import de Tailwind usa source(none), así que Tailwind solo ` +
|
|
363
|
+
`escanea las carpetas que indicas):\n ${result.line}`);
|
|
364
|
+
};
|
|
365
|
+
}
|
|
205
366
|
// ── Mensaje final ─────────────────────────────────────────────────────────────────────
|
|
367
|
+
/**
|
|
368
|
+
* M1: un componente que se actualizó y que usa un Block instalado (en `mimi.json → blocks`) que
|
|
369
|
+
* no se actualizó en esta ejecución (porque el usuario lo modificó, o porque no se pidió). Solo
|
|
370
|
+
* avisa: la pantalla es del usuario y puede necesitar cambios con la API nueva.
|
|
371
|
+
*/
|
|
372
|
+
function blocksUsingUpdated(plan, report, itemOf) {
|
|
373
|
+
const updated = new Set(report.updated.map(({ file }) => itemOf(file)));
|
|
374
|
+
const messages = [];
|
|
375
|
+
for (const block of Object.keys(plan.config.blocks ?? {}).sort()) {
|
|
376
|
+
if (updated.has(block) || registry_1.registry.items[block]?.type !== 'block')
|
|
377
|
+
continue;
|
|
378
|
+
const used = (0, registry_1.resolveItems)(block).filter((item) => updated.has(item) && registry_1.registry.items[item].type === 'component');
|
|
379
|
+
if (used.length === 0)
|
|
380
|
+
continue;
|
|
381
|
+
const list = used.join(', ');
|
|
382
|
+
messages.push(`Tu Block ${block} usa ${list}, que se actualizó a ${registry_1.registry.version}; revisa el ` +
|
|
383
|
+
'CHANGELOG por si tu pantalla necesita algún cambio.');
|
|
384
|
+
}
|
|
385
|
+
return messages;
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Cómo se importa un Block desde `app.routes.ts` (en `src/app/`): `src/app/blocks` →
|
|
389
|
+
* `./blocks/login-01`. Si la carpeta no está dentro de `app/`, la ruta del workspace.
|
|
390
|
+
*/
|
|
391
|
+
function routesImport(blocksDir, item) {
|
|
392
|
+
const at = `/${blocksDir}`.lastIndexOf('/app/');
|
|
393
|
+
return at >= 0 ? `./${`/${blocksDir}`.slice(at + 5)}/${item}` : `${blocksDir}/${item}`;
|
|
394
|
+
}
|
|
206
395
|
function summary(plan, report) {
|
|
207
396
|
return (_tree, context) => {
|
|
208
397
|
const log = context.logger;
|
|
209
|
-
const path = (file) =>
|
|
398
|
+
const path = (file) => (0, write_files_1.targetPathOf)(file, plan.componentsDir, plan.blocksDir);
|
|
210
399
|
if (report.created.length > 0) {
|
|
211
400
|
log.info('Agregado:');
|
|
212
401
|
for (const file of report.created)
|
|
@@ -216,17 +405,38 @@ function summary(plan, report) {
|
|
|
216
405
|
log.info('Reemplazado (--overwrite):');
|
|
217
406
|
for (const file of report.overwritten)
|
|
218
407
|
log.info(` ~ ${path(file)}`);
|
|
408
|
+
// Un Block es una pantalla del usuario: --overwrite la reemplaza entera (B.1, trampas).
|
|
409
|
+
const screens = [
|
|
410
|
+
...new Set(report.overwritten.filter(write_files_1.isBlockFile).map((file) => file.split('/')[1])),
|
|
411
|
+
];
|
|
412
|
+
for (const block of screens) {
|
|
413
|
+
log.warn(`--overwrite reemplazó tu pantalla ${block} (${plan.blocksDir}/${block}/) por la de ` +
|
|
414
|
+
`Mimi ${registry_1.registry.version}. Si tenías cambios, recupéralos de git.`);
|
|
415
|
+
}
|
|
219
416
|
}
|
|
220
417
|
const itemOf = (file) => [...plan.items, ...plan.implicit].find((item) => registry_1.registry.items[item].files.includes(file)) ??
|
|
221
418
|
file;
|
|
222
419
|
for (const message of (0, write_files_1.updatedMessages)(report, itemOf, registry_1.registry.version))
|
|
223
420
|
log.info(message);
|
|
421
|
+
for (const message of blocksUsingUpdated(plan, report, itemOf))
|
|
422
|
+
log.warn(message);
|
|
224
423
|
for (const file of report.skipped) {
|
|
225
424
|
log.warn(`Omitido: ${path(file)} ya existe con otro contenido (usa --overwrite para reemplazarlo).`);
|
|
226
425
|
}
|
|
227
|
-
for (const message of (0, write_files_1.blockedMessages)(report, plan.componentsDir, registry_1.registry.version)) {
|
|
426
|
+
for (const message of (0, write_files_1.blockedMessages)(report, plan.componentsDir, registry_1.registry.version, plan.blocksDir)) {
|
|
228
427
|
log.warn(message);
|
|
229
428
|
}
|
|
429
|
+
// Un Block nuevo es una página: se enruta (B.1, «API pública»).
|
|
430
|
+
const created = new Set(report.created);
|
|
431
|
+
for (const item of plan.items) {
|
|
432
|
+
const { type, files } = registry_1.registry.items[item];
|
|
433
|
+
if (type !== 'block' || !files.some((file) => created.has(file)))
|
|
434
|
+
continue;
|
|
435
|
+
const className = item.replace(/(^|-)([a-z0-9])/g, (_, _dash, c) => c.toUpperCase());
|
|
436
|
+
log.info(`${item} es una pantalla: agrégala a tus rutas, por ejemplo ` +
|
|
437
|
+
`{ path: '${item.split('-')[0]}', component: ${className} } ` +
|
|
438
|
+
`(import { ${className} } from '${routesImport(plan.blocksDir, item)}').`);
|
|
439
|
+
}
|
|
230
440
|
if (!(0, write_files_1.hasFileChanges)(report) && report.skipped.length === 0 && report.blocked.length === 0) {
|
|
231
441
|
log.info('Nada que hacer: los archivos ya están al día.');
|
|
232
442
|
}
|
|
@@ -236,6 +446,18 @@ function summary(plan, report) {
|
|
|
236
446
|
for (const [name, version] of packages)
|
|
237
447
|
log.info(` ${name}@${version}`);
|
|
238
448
|
}
|
|
449
|
+
if (plan.outdated.length > 0) {
|
|
450
|
+
const required = [...new Set(plan.outdated.map((peer) => peer.required))].join(', ');
|
|
451
|
+
const lines = plan.outdated.map((peer) => ` ${peer.name}: ${SOURCE_TEXT[peer.source](peer.current)}`);
|
|
452
|
+
log.warn(`Mimi necesita @angular/cdk y @angular/aria ${required} o posterior (las anteriores ` +
|
|
453
|
+
`tienen errores conocidos con sus componentes) y tu proyecto tiene:\n${lines.join('\n')}\n` +
|
|
454
|
+
`Actualízalos con:\n ng update ${plan.updatable.join(' ')}`);
|
|
455
|
+
}
|
|
456
|
+
if (plan.missingExclusion) {
|
|
457
|
+
const { cssPath, line } = plan.missingExclusion;
|
|
458
|
+
log.warn(`Tailwind escanea ${write_files_1.BASE_DIR}/ (las copias originales de Mimi) y genera CSS de clases que ` +
|
|
459
|
+
`quizá ya no usas. Agrega a ${cssPath}, después de @import "tailwindcss":\n ${line}`);
|
|
460
|
+
}
|
|
239
461
|
if ((0, write_files_1.hasFileChanges)(report)) {
|
|
240
462
|
log.info(`Guarda la carpeta ${write_files_1.BASE_DIR.split('/')[0]}/ en git: Mimi la usa para actualizar tus ` +
|
|
241
463
|
'componentes sin perder tus cambios.');
|