tailjng 0.0.61 → 0.1.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 (60) hide show
  1. package/README.md +186 -32
  2. package/cli/component-manager.js +71 -20
  3. package/cli/execute/init-app.js +251 -0
  4. package/cli/file-operations.js +45 -29
  5. package/cli/index.js +66 -15
  6. package/cli/settings/components-list.js +4 -147
  7. package/cli/settings/header-generator.js +9 -10
  8. package/cli/settings/lib-utils.js +89 -0
  9. package/cli/settings/overwrite-policy.js +18 -0
  10. package/cli/settings/path-utils.js +14 -29
  11. package/cli/settings/project-utils.js +220 -0
  12. package/cli/settings/prompt-utils.js +66 -5
  13. package/cli/templates/app.generator.js +382 -0
  14. package/fesm2022/tailjng.mjs +232 -66
  15. package/fesm2022/tailjng.mjs.map +1 -1
  16. package/lib/services/static/colors.service.d.ts +17 -0
  17. package/lib/services/transformer/transform.service.d.ts +3 -3
  18. package/package.json +1 -1
  19. package/public-api.d.ts +2 -0
  20. package/registry/components.json +164 -0
  21. package/src/lib/components/alert/alert-dialog/dialog-alert.component.css +17 -0
  22. package/src/lib/components/alert/alert-dialog/dialog-alert.component.html +83 -51
  23. package/src/lib/components/alert/alert-dialog/dialog-alert.component.ts +85 -53
  24. package/src/lib/components/alert/alert-toast/toast-alert.component.css +38 -4
  25. package/src/lib/components/alert/alert-toast/toast-alert.component.html +72 -40
  26. package/src/lib/components/alert/alert-toast/toast-alert.component.ts +84 -19
  27. package/src/lib/components/badge/badge.component.ts +1 -2
  28. package/src/lib/components/button/button.component.css +14 -0
  29. package/src/lib/components/button/button.component.html +17 -17
  30. package/src/lib/components/button/button.component.ts +139 -48
  31. package/src/lib/components/card/card-crud-complete/complete-crud-card.component.ts +5 -1
  32. package/src/lib/components/checkbox/checkbox-switch/switch-checkbox.component.html +1 -1
  33. package/src/lib/components/filter/filter-complete/complete-filter.component.html +1 -1
  34. package/src/lib/components/form/form-sidebar/sidebar-form.component.html +130 -97
  35. package/src/lib/components/form/form-sidebar/sidebar-form.component.ts +3 -2
  36. package/src/lib/components/input/input/input.component.css +35 -0
  37. package/src/lib/components/input/input/input.component.html +37 -27
  38. package/src/lib/components/input/input/input.component.ts +1 -0
  39. package/src/lib/components/menu/options-coach-menu/options-coach-menu.component.html +56 -0
  40. package/src/lib/components/menu/options-coach-menu/options-coach-menu.component.scss +12 -0
  41. package/src/lib/components/menu/options-coach-menu/options-coach-menu.component.ts +41 -0
  42. package/src/lib/components/select/select-dropdown/dropdown-select.component.css +4 -0
  43. package/src/lib/components/select/select-dropdown/dropdown-select.component.html +1 -1
  44. package/src/lib/components/select/select-dropdown/dropdown-select.component.ts +3 -3
  45. package/src/lib/components/select/select-multi-dropdown/multi-dropdown-select.component.css +4 -0
  46. package/src/lib/components/select/select-multi-dropdown/multi-dropdown-select.component.html +1 -1
  47. package/src/lib/components/select/select-multi-dropdown/multi-dropdown-select.component.ts +30 -20
  48. package/src/lib/components/table/table-crud-complete/complete-crud-table.component.html +506 -172
  49. package/src/lib/components/table/table-crud-complete/complete-crud-table.component.scss +92 -0
  50. package/src/lib/components/table/table-crud-complete/complete-crud-table.component.ts +141 -7
  51. package/src/lib/components/table/table-crud-complete/expand-grid/table-expand-grid.builder.ts +116 -0
  52. package/src/lib/components/table/table-crud-complete/expand-grid/table-expand-grid.helper.ts +43 -0
  53. package/src/lib/components/table/table-crud-complete/expand-grid/table-expand-grid.types.ts +39 -0
  54. package/src/lib/components/table/table-crud-complete/index.ts +3 -0
  55. package/src/lib/components/toggle-radio/toggle-radio.component.css +4 -0
  56. package/src/lib/components/toggle-radio/toggle-radio.component.html +4 -4
  57. package/src/lib/components/toggle-radio/toggle-radio.component.ts +15 -6
  58. package/src/lib/components/tooltip/tooltip.service.ts +0 -30
  59. package/tailjng-0.1.0.tgz +0 -0
  60. package/src/lib/components/color/colors.service.ts +0 -187
@@ -1,101 +1,134 @@
1
1
  <section class="content_form">
2
-
3
- <!-- Overlay -->
4
- @if (openForm) {
5
- <div onKeyPress class="fixed top-0 left-0 w-full h-full bg-black/50 z-[998] transition duration-300" (click)="canCloseOverlay ? onClose() : null"></div>
6
- }
7
-
8
- <!-- Sidebar personalizado -->
9
- @if (openForm) {
10
- <div @sidebarTransition
11
- class="fixed top-0 right-0 h-full max-w-full border border-border dark:border-dark-border rounded-tl-[15px] rounded-bl-[15px] text-white shadow-lg z-[999] flex flex-col"
12
- [style]="style"
13
- [ngClass]="bgColor"
14
- [class.w-[22em]]="size==='small'"
15
- [class.w-[30em]]="size==='medium'"
16
- [class.w-[47em]]="size==='large'"
17
- [class.w-[65em]]="size==='xlarge'"
2
+ <!-- Overlay -->
3
+ @if (openForm) {
4
+ <div
5
+ onKeyPress
6
+ class="fixed top-0 left-0 w-full h-full bg-black/50 z-[999] transition duration-300"
7
+ (click)="canCloseOverlay ? onClose() : null"
8
+ ></div>
9
+ }
10
+
11
+ <!-- Sidebar personalizado -->
12
+ @if (openForm) {
13
+ <div
14
+ @sidebarTransition
15
+ class="fixed top-0 right-0 h-full max-w-full border border-border dark:border-dark-border rounded-tl-[15px] rounded-bl-[15px] text-white shadow-lg z-[999] flex flex-col"
16
+ [style]="style"
17
+ [ngClass]="bgColor"
18
+ [class.w-[22em]]="size === 'small'"
19
+ [class.w-[30em]]="size === 'medium'"
20
+ [class.w-[47em]]="size === 'large'"
21
+ [class.w-[65em]]="size === 'xlarge'"
22
+ >
23
+ <!-- Header -->
24
+ <div
25
+ class="flex p-2 pl-4 pr-4 justify-between items-center bg-primary dark:bg-dark-primary border-b border-border dark:border-dark-border rounded-tl-[15px] font-semibold text-2sm"
26
+ >
27
+ <div class="flex items-center gap-2">
28
+ <span>
29
+ {{
30
+ typeForm !== "none"
31
+ ? typeForm === "create"
32
+ ? "AGREGAR"
33
+ : "ACTUALIZAR"
34
+ : "REGISTROS"
35
+ }}
36
+ {{ typeForm !== "none" ? titleForm : "" }}
37
+ </span>
38
+
39
+ <JButton
40
+ jTooltip="Información"
41
+ jTooltipPosition="bottom"
42
+ jCoachMark
43
+ [coachContent]="coachContentChangePassword"
44
+ coachPosition="bottom"
45
+ coachTrigger="click"
46
+ [icon]="iconsService.icons.info"
47
+ classes="border-none shadow-none p-0 m-0 h-5 w-5 text-white"
48
+ />
49
+ </div>
50
+
51
+ <ng-template #coachContentChangePassword>
52
+ <div class="text-[12px] leading-[15px]">
53
+ <span>
54
+ Las etiquetas con <span class="text-[10px]">✱</span> indican
55
+ campos requeridos.
56
+ </span>
57
+ <br />
58
+ <span>
59
+ <span class="text-red-600 dark:text-red-300">✱</span>
60
+ Obligatorio
61
+ </span>
62
+ <br />
63
+ <span>
64
+ <span class="text-blue-600 dark:text-blue-300">✱</span>
65
+ Condicionado
66
+ </span>
67
+ <br />
68
+ <span>
69
+ <span class="text-purple-600 dark:text-purple-300">✱</span>
70
+ Automático
71
+ </span>
72
+ </div>
73
+ </ng-template>
74
+
75
+ <div class="flex gap-2">
76
+ @for (cb of checkboxes; track $index) {
77
+ @if (cb.isVisible) {
78
+ <JSwitchCheckbox
79
+ onKeyPress
80
+ [title]="cb.title"
81
+ [isChecked]="cb.isChecked"
82
+ [isLoading]="cb.isLoading"
83
+ (click)="cb.onClick(!cb.isChecked, $index)"
84
+ />
85
+ }
86
+ }
87
+ </div>
88
+
89
+ <button
90
+ type="button"
91
+ (click)="onClose()"
92
+ class="p-2 rounded-full border border-border dark:border-dark-border text-white hover:bg-dark-background focus:outline-none cursor-pointer"
18
93
  >
19
- <!-- Header -->
20
- <div class="flex p-2 pl-4 pr-4 justify-between items-center bg-primary dark:bg-dark-primary border-b border-border dark:border-dark-border rounded-tl-[15px] font-semibold text-2sm">
21
- <div class="flex items-center gap-2">
22
- <span>
23
- {{ typeForm !== 'none' ? typeForm === 'create' ? 'AGREGAR' : 'ACTUALIZAR' : 'REGISTROS' }} {{ typeForm !== 'none' ? titleForm : '' }}
24
- </span>
25
-
26
- <div class="cursor-pointer"
27
- [jTooltip]="tooltipContentChangePassword"
28
- [jTooltipPosition]="'bottom'"
29
- [jTooltipOffsetX]="25"
30
- [jTooltipOffsetY]="-20"
31
- >
32
- <lucide-icon [name]="iconsService.icons.info" [size]="15"></lucide-icon>
33
- </div>
34
- </div>
35
-
36
-
37
- <ng-template #tooltipContentChangePassword>
38
- <div class="text-[12px] leading-[15px]">
39
- <span>Las etiquetas con ✱ indican campos requeridos.</span> <br>
40
- <span><span class="text-red-600 dark:text-red-300">✱</span> Obligatorio</span> <br>
41
- <span><span class="text-blue-600 dark:text-blue-300">✱</span> Condicionado</span> <br>
42
- <span><span class="text-purple-600 dark:text-purple-300">✱</span> Automático</span> <br>
43
- </div>
44
- </ng-template>
45
-
46
-
47
- <div class="flex gap-2">
48
- @for (cb of checkboxes; track $index) {
49
- @if (cb.isVisible) {
50
- <JSwitchCheckbox onKeyPress
51
- [title]="cb.title"
52
- [isChecked]="cb.isChecked"
53
- [isLoading]="cb.isLoading"
54
- (click)="cb.onClick(!cb.isChecked, $index)"
55
- />
56
- }
57
- }
58
- </div>
59
-
60
- <button type="button" (click)="onClose()" class="p-2 rounded-full border border-border dark:border-dark-border text-white hover:bg-dark-background focus:outline-none cursor-pointer">
61
- <lucide-icon [name]="iconsService.icons.close" size="16"></lucide-icon>
62
- </button>
63
- </div>
64
-
65
-
66
- <!-- Content -->
67
- <div class="p-4 flex-1 overflow-x-hidden overflow-y-auto scroll-element">
68
- @if (formTemplate) {
69
- <ng-container [ngTemplateOutlet]="formTemplate"></ng-container>
70
- }
71
- </div>
72
-
73
-
74
- <!-- Footer -->
75
- @if (typeForm !== 'none') {
76
- <div class="p-4 border-t border-border dark:border-dark-border rounded-bl-[15px] flex justify-center gap-3">
77
-
78
- <JButton
79
- type="submit"
80
- (clicked)="onSubmit()"
81
- classes="primary"
82
- [icon]="iconsService.icons.save"
83
- [isLoading]="isLoading"
84
- >
85
- {{typeForm === 'create' ? 'AGREGAR' : 'ACTUALIZAR'}}
86
- </JButton>
87
-
88
- <JButton
89
- type="button"
90
- (clicked)="onClose()"
91
- classes="secondary"
92
- [icon]="iconsService.icons.error"
93
- text="CANCELAR"
94
- />
95
-
96
- </div>
94
+ <lucide-icon
95
+ [name]="iconsService.icons.close"
96
+ size="16"
97
+ ></lucide-icon>
98
+ </button>
99
+ </div>
100
+
101
+ <!-- Content -->
102
+ <div class="p-4 flex-1 overflow-x-hidden overflow-y-auto scroll-element">
103
+ @if (formTemplate) {
104
+ <ng-container [ngTemplateOutlet]="formTemplate"></ng-container>
97
105
  }
98
- </div>
99
- }
106
+ </div>
100
107
 
101
- </section>
108
+ <!-- Footer -->
109
+ @if (typeForm !== "none") {
110
+ <div
111
+ class="p-4 border-t border-border dark:border-dark-border rounded-bl-[15px] flex justify-center gap-3"
112
+ >
113
+ <JButton
114
+ type="submit"
115
+ (clicked)="onSubmit()"
116
+ classes="primary"
117
+ [icon]="iconsService.icons.save"
118
+ [isLoading]="isLoading"
119
+ >
120
+ {{ typeForm === "create" ? "AGREGAR" : "ACTUALIZAR" }}
121
+ </JButton>
122
+
123
+ <JButton
124
+ type="button"
125
+ (clicked)="onClose()"
126
+ classes="secondary"
127
+ [icon]="iconsService.icons.error"
128
+ text="CANCELAR"
129
+ />
130
+ </div>
131
+ }
132
+ </div>
133
+ }
134
+ </section>
@@ -7,11 +7,12 @@ import { DynamicCheckbox, FormType, JIconsService } from 'tailjng';
7
7
  import { JTooltipDirective } from '../../tooltip/tooltip.directive';
8
8
  import { JButtonComponent } from '../../button/button.component';
9
9
  import { JSwitchCheckboxComponent } from '../../checkbox/checkbox-switch/switch-checkbox.component';
10
+ import { JCoachMarkDirective } from '../../coach-mark/coach-mark.directive';
10
11
 
11
12
  @Component({
12
13
  selector: 'JSidebarForm',
13
14
  standalone: true,
14
- imports: [LucideAngularModule, JButtonComponent, JTooltipDirective, ReactiveFormsModule, FormsModule, CommonModule, JSwitchCheckboxComponent],
15
+ imports: [LucideAngularModule, JButtonComponent, JTooltipDirective, ReactiveFormsModule, FormsModule, CommonModule, JSwitchCheckboxComponent, JCoachMarkDirective],
15
16
  templateUrl: './sidebar-form.component.html',
16
17
  styleUrl: './sidebar-form.component.scss',
17
18
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
@@ -61,4 +62,4 @@ export class JSidebarFormComponent {
61
62
  this.onClose();
62
63
  }
63
64
  }
64
- }
65
+ }
@@ -0,0 +1,35 @@
1
+ @media (max-width: 400px) {
2
+ .input-container {
3
+ width: 100%;
4
+ }
5
+
6
+ input[type="date"] {
7
+ appearance: none !important;
8
+ -webkit-appearance: none !important;
9
+ }
10
+
11
+ input[type="date"]::-webkit-calendar-picker-indicator {
12
+ opacity: 0 !important;
13
+ display: none !important;
14
+ -webkit-appearance: none !important;
15
+ }
16
+
17
+ input[type="date"]:not(:valid) {
18
+ color: transparent !important;
19
+ }
20
+
21
+ input[type="date"]:not(:valid)::before {
22
+ content: attr(placeholder) !important;
23
+ color: rgb(148 163 184) !important;
24
+ }
25
+ }
26
+
27
+ .date-fake-placeholder {
28
+ display: none;
29
+ }
30
+
31
+ @media (pointer: coarse) {
32
+ .date-fake-placeholder {
33
+ display: block;
34
+ }
35
+ }
@@ -1,29 +1,39 @@
1
1
  <div class="relative w-full">
2
- <input
3
- [type]="type"
4
- [id]="id"
5
- [name]="name ?? ''"
6
- [placeholder]="placeholder"
7
- [value]="value"
8
- (input)="onInput($event)"
9
- [required]="required"
10
- [disabled]="isDisabled"
11
- (blur)="onTouched()"
12
- [ngClass]="combinedNgClass"
13
- [class]="classes"
14
- class="input w-full h-[40px] max-[400px]:h-[35px] text-[12px] bg-background dark:bg-dark-background border border-border dark:border-dark-border text-black dark:text-white placeholder:text-muted-foreground/70 dark:placeholder:text-dark-muted-foreground/70 rounded px-3 py-2 focus:outline-none focus:ring-2 focus:ring-primary dark:focus:ring-dark-primary transition duration-200 resize-y disabled:opacity-60 disabled:cursor-not-allowed"
15
- />
2
+ @if (type === 'date' && !value && placeholder) {
3
+ <span
4
+ class="date-fake-placeholder pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-[12px] text-muted-foreground/70 dark:text-dark-muted-foreground/70 z-[1]"
5
+ >
6
+ {{ placeholder }}
7
+ </span>
8
+ }
16
9
 
17
- @if (value && clearButton) {
18
- <button type="button"
19
- class="absolute right-2 top-1/2 -translate-y-1/2 text-gray-400 text-gray-400 hover:text-gray-500 pr-1 mr-1 text-gray-400 hover:text-gray-600 focus:outline-none cursor-pointer"
20
- (click)="clearInput()"
21
- [disabled]="isDisabled"
22
- [ngClass]="{
23
- 'cursor-not-allowed opacity-50': isDisabled
24
- }"
25
- >
26
- <lucide-icon [name]="iconsService.icons.close" class="w-4 h-4" />
27
- </button>
28
- }
29
- </div>
10
+ <input
11
+ [type]="type"
12
+ [id]="id"
13
+ [name]="name ?? ''"
14
+ [placeholder]="placeholder"
15
+ [value]="value"
16
+ (input)="onInput($event)"
17
+ [required]="required"
18
+ [disabled]="isDisabled"
19
+ [readonly]="isReadonly"
20
+ (blur)="onTouched()"
21
+ [ngClass]="combinedNgClass"
22
+ [class]="classes"
23
+ class="input w-full h-[40px] max-[400px]:h-[35px] text-[12px] bg-background dark:bg-dark-background border border-border dark:border-dark-border text-black dark:text-white placeholder:text-muted-foreground/70 dark:placeholder:text-dark-muted-foreground/70 rounded px-3 py-2 focus:outline-none focus:ring-2 focus:ring-primary dark:focus:ring-dark-primary transition duration-200 resize-y disabled:opacity-60 disabled:cursor-not-allowed"
24
+ />
25
+
26
+ @if (value && clearButton) {
27
+ <button
28
+ type="button"
29
+ class="absolute right-2 top-1/2 -translate-y-1/2 text-gray-400 text-gray-400 hover:text-gray-500 pr-1 mr-1 text-gray-400 hover:text-gray-600 focus:outline-none cursor-pointer"
30
+ (click)="clearInput()"
31
+ [disabled]="isDisabled"
32
+ [ngClass]="{
33
+ 'cursor-not-allowed opacity-50': isDisabled,
34
+ }"
35
+ >
36
+ <lucide-icon [name]="iconsService.icons.close" class="w-4 h-4" />
37
+ </button>
38
+ }
39
+ </div>
@@ -26,6 +26,7 @@ export class JInputComponent implements ControlValueAccessor {
26
26
  @Input() placeholder: string = '';
27
27
 
28
28
  @Input() isDisabled: boolean = false;
29
+ @Input() isReadonly: boolean = false;
29
30
  @Input() required: boolean = false;
30
31
  @Input() clearButton: boolean = false;
31
32
 
@@ -0,0 +1,56 @@
1
+ <div class="relative isolate">
2
+ <JButton
3
+ onKeyPress
4
+ jCoachMark
5
+ [contentTemplate]="customTemplateActions"
6
+ [isDescriptionFormatted]="true"
7
+ coachPosition="bottom-left"
8
+ coachTrigger="click"
9
+ [coachMaxWidth]="'170px'"
10
+ [coachWidth]="'170px'"
11
+ [coachSpotlight]="false"
12
+ [icon]="iconsService.icons.ellipsisVertical"
13
+ [iconSize]="20"
14
+ classes="secondary w-[35px] h-[35px] rounded-full"
15
+ (clicked)="handleClickGeneral()"
16
+ />
17
+
18
+ <ng-template #customTemplateActions>
19
+ <div class="menu-options-list flex flex-col gap-2 w-full min-w-full">
20
+ @if (optionsTable.length > 0) {
21
+ @for (option of optionsTable; track $index) {
22
+ @if (getIsVisible(option, group)) {
23
+ <div
24
+ class="menu-option-item w-full hover:bg-primary/10 dark:hover:bg-dark-primary/10 text-[10px]"
25
+ >
26
+ <JButton
27
+ onKeyPress
28
+ size="sm"
29
+ [icon]="getIcon(option.icon, group)"
30
+ [iconSize]="16"
31
+ (clicked)="handleClick(option, group)"
32
+ [tooltip]="getTooltip(option.tooltip ?? '', group)"
33
+ [tooltipPosition]="option.tooltipPosition ?? 'top'"
34
+ [disabled]="getDisabled(option, group)"
35
+ [isLoading]="false"
36
+ [classes]="
37
+ 'w-full min-w-full text-left justify-start h-[30px] ' +
38
+ (option.classes ?? '')
39
+ "
40
+ [ngClasses]="mergeNgClasses(option.ngClass, group)"
41
+ >
42
+ {{ getTooltip(option.text ?? '', group) }}
43
+ </JButton>
44
+ </div>
45
+ }
46
+ }
47
+ } @else {
48
+ <div
49
+ class="text-center text-gray-500 dark:text-dark-text-secondary text-xs"
50
+ >
51
+ No hay opciones disponibles
52
+ </div>
53
+ }
54
+ </div>
55
+ </ng-template>
56
+ </div>
@@ -0,0 +1,12 @@
1
+ .menu-options-list,
2
+ .menu-option-item {
3
+ width: 100%;
4
+ min-width: 100%;
5
+ }
6
+
7
+ :host ::ng-deep .menu-option-item j-button,
8
+ :host ::ng-deep .menu-option-item jbutton {
9
+ display: flex;
10
+ width: 100%;
11
+ min-width: 0;
12
+ }
@@ -0,0 +1,41 @@
1
+ import { Component, Input, Output, EventEmitter, ElementRef, OnDestroy, ViewChild, AfterViewInit, Renderer2 } from '@angular/core';
2
+ import { LucideAngularModule } from 'lucide-angular';
3
+ import { JButtonComponent } from '../../button/button.component';
4
+ import { JIconsService } from 'tailjng';
5
+ import { JCoachMarkDirective } from '../../coach-mark/coach-mark.directive';
6
+
7
+ @Component({
8
+ selector: 'JOptionsCoachMenu',
9
+ imports: [LucideAngularModule, JButtonComponent, JCoachMarkDirective],
10
+ templateUrl: './options-coach-menu.component.html',
11
+ styleUrl: './options-coach-menu.component.scss'
12
+ })
13
+ export class JOptionsCoachMenuComponent {
14
+
15
+ @Input() optionsTable: any[] = [];
16
+ @Input() group: any;
17
+ @Input() rowId: string | number | null = null;
18
+
19
+ @Input() getIcon: (icon: any, group: any) => any = () => null;
20
+ @Input() getTooltip: (tooltip: string, group: any) => string = () => '';
21
+ @Input() getDisabled: (option: any, group: any) => boolean = () => false;
22
+ @Input() getIsVisible: (option: any, group: any) => boolean = () => true;
23
+ @Input() mergeNgClasses: (ngClass: any, group: any) => any = () => '';
24
+ @Input() isAditionalButtonLoading: (type: string, id: any) => boolean = () => false;
25
+
26
+ @Output() clicked = new EventEmitter<{ option: any; group: any }>();
27
+
28
+
29
+ constructor(
30
+ public readonly iconsService: JIconsService,
31
+ ) { }
32
+
33
+ handleClick(option: any, group: any): void {
34
+ this.clicked.emit({ option, group });
35
+ this.handleClickGeneral();
36
+ }
37
+
38
+ handleClickGeneral(): void {
39
+ document.body.click();
40
+ }
41
+ }
@@ -0,0 +1,4 @@
1
+ :host {
2
+ display: block;
3
+ width: 100%;
4
+ }
@@ -1,4 +1,4 @@
1
- <div class="relative w-full h-full">
1
+ <div class="relative w-full">
2
2
  <div #selectButton class="w-auto">
3
3
  <button
4
4
  type="button"
@@ -1,5 +1,5 @@
1
1
 
2
- import { Component, Input, Output, EventEmitter, ElementRef, ViewChild, OnDestroy, ChangeDetectorRef, AfterViewInit, OnInit, SimpleChanges, OnChanges, } from "@angular/core"
2
+ import { Component, Input, Output, EventEmitter, ElementRef, ViewChild, OnDestroy, ChangeDetectorRef, AfterViewInit, OnInit, SimpleChanges, OnChanges, Optional, } from "@angular/core"
3
3
  import { FormsModule, ControlValueAccessor, ReactiveFormsModule, NG_VALUE_ACCESSOR } from "@angular/forms"
4
4
  import { CommonModule } from "@angular/common"
5
5
  import { LucideAngularModule } from "lucide-angular"
@@ -123,7 +123,7 @@ export class JDropdownSelectComponent implements ControlValueAccessor, AfterView
123
123
  public readonly iconsService: JIconsService,
124
124
  private readonly cdr: ChangeDetectorRef,
125
125
  private readonly elementRef: ElementRef,
126
- private readonly genericService: JGenericCrudService,
126
+ @Optional() private readonly genericService: JGenericCrudService | null,
127
127
  ) { }
128
128
 
129
129
  ngOnInit() {
@@ -270,7 +270,7 @@ export class JDropdownSelectComponent implements ControlValueAccessor, AfterView
270
270
  * @returns
271
271
  */
272
272
  loadData() {
273
- if (!this.endpoint) return
273
+ if (!this.endpoint || !this.genericService) return
274
274
 
275
275
  this.isLoading = true
276
276
  const params: any = {}
@@ -0,0 +1,4 @@
1
+ :host {
2
+ display: block;
3
+ width: 100%;
4
+ }
@@ -1,4 +1,4 @@
1
- <div class="relative w-full h-full">
1
+ <div class="relative w-full">
2
2
  <div class="w-auto" #selectButton>
3
3
  <button
4
4
  type="button"
@@ -1,4 +1,4 @@
1
- import { Component, Input, Output, EventEmitter, ElementRef, ViewChild, OnDestroy, ChangeDetectorRef, AfterViewInit, OnInit, SimpleChanges, OnChanges, } from "@angular/core"
1
+ import { Component, Input, Output, EventEmitter, ElementRef, ViewChild, OnDestroy, ChangeDetectorRef, AfterViewInit, OnInit, SimpleChanges, OnChanges, Optional, } from "@angular/core"
2
2
  import { FormsModule, type ControlValueAccessor, ReactiveFormsModule, NG_VALUE_ACCESSOR } from "@angular/forms"
3
3
  import { CommonModule } from "@angular/common"
4
4
  import { LucideAngularModule } from "lucide-angular"
@@ -124,7 +124,7 @@ export class JMultiDropdownSelectComponent implements ControlValueAccessor, Afte
124
124
  public readonly iconsService: JIconsService,
125
125
  private readonly cdr: ChangeDetectorRef,
126
126
  private readonly elementRef: ElementRef,
127
- private readonly genericService: JGenericCrudService,
127
+ @Optional() private readonly genericService: JGenericCrudService | null,
128
128
  ) { }
129
129
 
130
130
  ngOnInit() {
@@ -187,9 +187,10 @@ export class JMultiDropdownSelectComponent implements ControlValueAccessor, Afte
187
187
  const text = Array.isArray(this.optionLabel)
188
188
  ? this.optionLabel.map((k) => this.getNestedValue(option, k)).join(this.labelSeparator)
189
189
  : this.getNestedValue(option, this.optionLabel);
190
- return { value: option[this.optionValue], text, original: option };
190
+ return { value: this.normalizeOptionValue(option[this.optionValue]), text, original: option };
191
191
  });
192
- } else {
192
+ } else if (!(this.type === 'searchable' && this.endpoint)) {
193
+ // No vaciar opciones ya cargadas desde API (modo remoto)
193
194
  this.processedOptions = [];
194
195
  }
195
196
 
@@ -204,7 +205,7 @@ export class JMultiDropdownSelectComponent implements ControlValueAccessor, Afte
204
205
  // Carga desde API
205
206
  // ============================
206
207
  loadData() {
207
- if (!this._finalEndpoint) return;
208
+ if (!this._finalEndpoint || !this.genericService) return;
208
209
 
209
210
  this.isLoading = true;
210
211
 
@@ -226,16 +227,16 @@ export class JMultiDropdownSelectComponent implements ControlValueAccessor, Afte
226
227
  this.genericService.findAll<any>({ endpoint: this._finalEndpoint, params }).subscribe({
227
228
  next: (resp) => {
228
229
  const data = resp?.data?.[this.mainEndpoint] ?? resp?.data ?? [];
229
- // mapea a processedOptions
230
- this.processedOptions = Array.isArray(data)
231
- ? data.map((option: any) => ({
232
- value: option?.[this.optionValue],
233
- text: Array.isArray(this.optionLabel)
234
- ? this.optionLabel.map((k) => this.getNestedValue(option, k)).filter(Boolean).join(this.labelSeparator)
235
- : this.getNestedValue(option, this.optionLabel),
236
- original: option,
237
- }))
238
- : [];
230
+ const list = Array.isArray(data) ? data : [];
231
+ // Mantener @Input options sincronizado (igual que JDropdownSelect) para processOptions()
232
+ this.options = list;
233
+ this.processedOptions = list.map((option: any) => ({
234
+ value: this.normalizeOptionValue(option?.[this.optionValue]),
235
+ text: Array.isArray(this.optionLabel)
236
+ ? this.optionLabel.map((k) => this.getNestedValue(option, k)).filter(Boolean).join(this.labelSeparator)
237
+ : this.getNestedValue(option, this.optionLabel),
238
+ original: option,
239
+ }));
239
240
 
240
241
  this.filteredProcessedOptions = [...this.processedOptions];
241
242
  this.fullData.emit(data);
@@ -367,7 +368,7 @@ export class JMultiDropdownSelectComponent implements ControlValueAccessor, Afte
367
368
  this.onTouched();
368
369
  this.updateDropdownPosition();
369
370
 
370
- if (this.type === 'searchable' && (this.loadOpen || (!this.loadOnInit && this.processedOptions.length === 0))) {
371
+ if (this.type === 'searchable' && (this.loadOpen || this.processedOptions.length === 0)) {
371
372
  this.loadData();
372
373
  }
373
374
  }
@@ -588,10 +589,19 @@ export class JMultiDropdownSelectComponent implements ControlValueAccessor, Afte
588
589
  * Write the value to the component
589
590
  * @param value
590
591
  */
591
- writeValue(value: any[]): void {
592
- this.selectedValues = Array.isArray(value) ? value : []
593
- this.updateDisplayLabel()
594
- this.cdr.markForCheck()
592
+ writeValue(value: any[] | null): void {
593
+ this.selectedValues = Array.isArray(value)
594
+ ? value.map((item) => this.normalizeOptionValue(item))
595
+ : [];
596
+ this.updateDisplayLabel();
597
+ this.cdr.markForCheck();
598
+ }
599
+
600
+ private normalizeOptionValue(value: unknown): unknown {
601
+ if (typeof value === 'string' && value.trim() !== '' && !Number.isNaN(Number(value))) {
602
+ return Number(value);
603
+ }
604
+ return value;
595
605
  }
596
606
 
597
607