tailjng 0.1.6 → 0.1.7

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 (176) hide show
  1. package/README.md +12 -4
  2. package/cli/execute/init-app.js +5 -2
  3. package/cli/execute/sync-app.js +14 -2
  4. package/cli/settings/colors-config-utils.js +43 -8
  5. package/cli/settings/icons-config-utils.js +62 -0
  6. package/cli/settings/path-utils.js +32 -2
  7. package/cli/settings/project-utils.js +7 -1
  8. package/cli/templates/app.generator.js +2 -2
  9. package/fesm2022/tailjng.mjs +247 -80
  10. package/fesm2022/tailjng.mjs.map +1 -1
  11. package/lib/services/static/theme.service.d.ts +39 -1
  12. package/lib/utils/theme/theme-variables.util.d.ts +31 -0
  13. package/package.json +1 -1
  14. package/public-api.d.ts +2 -1
  15. package/registry/components.json +41 -18
  16. package/src/colors.safelist.css +2 -2
  17. package/src/lib/components/.config/README.md +11 -0
  18. package/src/lib/components/.config/colors/README.md +38 -0
  19. package/src/lib/components/{colors-config → .config/colors}/colors.config.ts +5 -5
  20. package/src/lib/components/{colors-config → .config/colors}/colors.safelist.css +2 -2
  21. package/src/lib/components/.config/icons/README.md +26 -0
  22. package/src/lib/components/.config/icons/icons.lucide.ts +134 -0
  23. package/src/lib/components/.config/input/README.md +24 -0
  24. package/src/lib/components/.config/input/input.classes.ts +119 -0
  25. package/src/lib/components/alert/alert-dialog/dialog-alert.component.css +244 -2
  26. package/src/lib/components/alert/alert-dialog/dialog-alert.component.html +25 -38
  27. package/src/lib/components/alert/alert-dialog/dialog-alert.component.ts +66 -56
  28. package/src/lib/components/alert/alert-dialog/dialog-alert.types.ts +19 -0
  29. package/src/lib/components/alert/alert-toast/toast-alert.component.css +630 -12
  30. package/src/lib/components/alert/alert-toast/toast-alert.component.html +103 -102
  31. package/src/lib/components/alert/alert-toast/toast-alert.component.ts +485 -128
  32. package/src/lib/components/alert/alert-toast/toast-alert.types.ts +25 -0
  33. package/src/lib/components/badge/badge.component.html +34 -21
  34. package/src/lib/components/badge/badge.component.ts +140 -31
  35. package/src/lib/components/button/button.component.html +16 -10
  36. package/src/lib/components/button/button.component.ts +162 -22
  37. package/src/lib/components/card/card-complete/complete-card.component.html +2 -2
  38. package/src/lib/components/card/card-complete/complete-card.component.ts +26 -16
  39. package/src/lib/components/card/card-crud-complete/complete-crud-card.component.html +2 -2
  40. package/src/lib/components/card/card-crud-complete/complete-crud-card.component.ts +26 -16
  41. package/src/lib/components/checkbox/checkbox-input/input-checkbox.component.css +97 -0
  42. package/src/lib/components/checkbox/checkbox-input/input-checkbox.component.html +54 -46
  43. package/src/lib/components/checkbox/checkbox-input/input-checkbox.component.ts +135 -64
  44. package/src/lib/components/checkbox/checkbox-input/input-checkbox.types.ts +3 -0
  45. package/src/lib/components/checkbox/checkbox-switch/switch-checkbox.component.css +112 -0
  46. package/src/lib/components/checkbox/checkbox-switch/switch-checkbox.component.html +28 -25
  47. package/src/lib/components/checkbox/checkbox-switch/switch-checkbox.component.ts +67 -15
  48. package/src/lib/components/checkbox/checkbox-switch/switch-checkbox.types.ts +1 -0
  49. package/src/lib/components/coach-mark/coach-mark.component.html +4 -22
  50. package/src/lib/components/coach-mark/coach-mark.component.scss +1 -1
  51. package/src/lib/components/coach-mark/coach-mark.component.ts +51 -18
  52. package/src/lib/components/coach-mark/coach-mark.directive.ts +133 -78
  53. package/src/lib/components/coach-mark/coach-mark.types.ts +12 -0
  54. package/src/lib/components/dialog/dialog.component.css +103 -1
  55. package/src/lib/components/dialog/dialog.component.html +46 -66
  56. package/src/lib/components/dialog/dialog.component.ts +136 -110
  57. package/src/lib/components/dialog/dialog.types.ts +19 -0
  58. package/src/lib/components/filter/filter-complete/complete-filter.component.html +16 -19
  59. package/src/lib/components/filter/filter-complete/complete-filter.component.scss +35 -0
  60. package/src/lib/components/filter/filter-complete/complete-filter.component.ts +58 -34
  61. package/src/lib/components/filter/filter-complete/complete-filter.types.ts +7 -0
  62. package/src/lib/components/filter/filter-complete/complete-filter.util.ts +16 -0
  63. package/src/lib/components/form/form-container/container-form.component.css +4 -0
  64. package/src/lib/components/form/form-container/container-form.component.html +2 -2
  65. package/src/lib/components/form/form-container/container-form.component.ts +72 -16
  66. package/src/lib/components/form/form-container/container-form.types.ts +42 -0
  67. package/src/lib/components/form/form-container/form-col-span.directive.ts +25 -0
  68. package/src/lib/components/form/form-sidebar/sidebar-form.component.css +276 -0
  69. package/src/lib/components/form/form-sidebar/sidebar-form.component.html +117 -125
  70. package/src/lib/components/form/form-sidebar/sidebar-form.component.ts +109 -34
  71. package/src/lib/components/form/form-sidebar/sidebar-form.types.ts +3 -0
  72. package/src/lib/components/{toggle-radio/toggle-radio.component.css → form/form-validation/validation-form.component.css} +0 -1
  73. package/src/lib/components/form/form-validation/validation-form.component.html +10 -6
  74. package/src/lib/components/form/form-validation/validation-form.component.ts +99 -12
  75. package/src/lib/components/form/form-validation/validation-form.types.ts +33 -0
  76. package/src/lib/components/icon/icon.component.html +8 -5
  77. package/src/lib/components/icon/icon.component.ts +111 -9
  78. package/src/lib/components/input/input/input.component.html +19 -16
  79. package/src/lib/components/input/input/input.component.ts +130 -53
  80. package/src/lib/components/input/input/input.types.ts +8 -0
  81. package/src/lib/components/input/input-file/file-input.component.html +65 -56
  82. package/src/lib/components/input/input-file/file-input.component.ts +276 -173
  83. package/src/lib/components/input/input-file/file-input.types.ts +2 -0
  84. package/src/lib/components/input/input-range/range-input.component.css +67 -0
  85. package/src/lib/components/input/input-range/range-input.component.html +50 -58
  86. package/src/lib/components/input/input-range/range-input.component.ts +148 -60
  87. package/src/lib/components/input/input-range/range-input.types.ts +7 -0
  88. package/src/lib/components/input/input-textarea/textarea-input.component.html +16 -7
  89. package/src/lib/components/input/input-textarea/textarea-input.component.ts +140 -50
  90. package/src/lib/components/input/input-textarea/textarea-input.types.ts +2 -0
  91. package/src/lib/components/label/label.component.html +17 -16
  92. package/src/lib/components/label/label.component.ts +70 -16
  93. package/src/lib/components/label/label.types.ts +2 -0
  94. package/src/lib/components/menu/menu-options-table/menu-options-defaults.ts +34 -0
  95. package/src/lib/components/menu/menu-options-table/options-table-menu.component.html +34 -20
  96. package/src/lib/components/menu/menu-options-table/options-table-menu.component.ts +211 -58
  97. package/src/lib/components/menu/menu-options-table/options-table-menu.types.ts +38 -0
  98. package/src/lib/components/menu/options-coach-menu/options-coach-menu.component.html +49 -52
  99. package/src/lib/components/menu/options-coach-menu/options-coach-menu.component.ts +112 -24
  100. package/src/lib/components/menu/options-coach-menu/options-coach-menu.types.ts +9 -0
  101. package/src/lib/components/mode-toggle/mode-toggle.component.html +11 -16
  102. package/src/lib/components/mode-toggle/mode-toggle.component.ts +69 -33
  103. package/src/lib/components/paginator/paginator-complete/complete-paginator.component.html +4 -4
  104. package/src/lib/components/paginator/paginator-complete/complete-paginator.component.ts +31 -7
  105. package/src/lib/components/paginator/paginator-complete/complete-paginator.types.ts +12 -0
  106. package/src/lib/components/paginator/paginator-complete/complete-paginator.util.ts +36 -0
  107. package/src/lib/components/progress-bar/progress-bar.component.css +11 -0
  108. package/src/lib/components/progress-bar/progress-bar.component.html +41 -40
  109. package/src/lib/components/progress-bar/progress-bar.component.ts +95 -11
  110. package/src/lib/components/progress-bar/progress-bar.types.ts +2 -0
  111. package/src/lib/components/select/select-dropdown/dropdown-select.component.css +6 -0
  112. package/src/lib/components/select/select-dropdown/dropdown-select.component.html +54 -44
  113. package/src/lib/components/select/select-dropdown/dropdown-select.component.ts +450 -509
  114. package/src/lib/components/select/select-dropdown/dropdown-select.types.ts +43 -0
  115. package/src/lib/components/select/select-dropdown/dropdown-select.util.ts +179 -0
  116. package/src/lib/components/select/select-multi-dropdown/multi-dropdown-select.component.css +6 -0
  117. package/src/lib/components/select/select-multi-dropdown/multi-dropdown-select.component.html +131 -42
  118. package/src/lib/components/select/select-multi-dropdown/multi-dropdown-select.component.ts +491 -475
  119. package/src/lib/components/select/select-multi-dropdown/multi-dropdown-select.types.ts +22 -0
  120. package/src/lib/components/select/select-multi-dropdown/multi-dropdown-select.util.ts +20 -0
  121. package/src/lib/components/select/select-multi-table/multi-table-select.component.css +10 -0
  122. package/src/lib/components/select/select-multi-table/multi-table-select.component.html +76 -60
  123. package/src/lib/components/select/select-multi-table/multi-table-select.component.ts +250 -313
  124. package/src/lib/components/select/select-multi-table/multi-table-select.types.ts +10 -0
  125. package/src/lib/components/select/select-multi-table/multi-table-select.util.ts +5 -0
  126. package/src/lib/components/sidebar/sidebar-static/static-sidebar.component.css +212 -0
  127. package/src/lib/components/sidebar/sidebar-static/static-sidebar.component.html +62 -53
  128. package/src/lib/components/sidebar/sidebar-static/static-sidebar.component.ts +84 -27
  129. package/src/lib/components/sidebar/sidebar-static/static-sidebar.types.ts +2 -0
  130. package/src/lib/components/table/table-complete/complete-table.component.html +15 -17
  131. package/src/lib/components/table/table-complete/complete-table.component.ts +190 -338
  132. package/src/lib/components/table/table-complete/complete-table.types.ts +28 -0
  133. package/src/lib/components/table/table-complete/complete-table.util.ts +236 -0
  134. package/src/lib/components/table/table-complete/index.ts +2 -0
  135. package/src/lib/components/table/table-crud-complete/complete-crud-table.animations.ts +34 -0
  136. package/src/lib/components/table/table-crud-complete/complete-crud-table.component.html +73 -128
  137. package/src/lib/components/table/table-crud-complete/complete-crud-table.component.ts +542 -829
  138. package/src/lib/components/table/table-crud-complete/complete-crud-table.types.ts +57 -0
  139. package/src/lib/components/table/table-crud-complete/complete-crud-table.util.ts +723 -0
  140. package/src/lib/components/table/table-crud-complete/index.ts +3 -0
  141. package/src/lib/components/theme-generator/theme-generator.component.css +21 -0
  142. package/src/lib/components/theme-generator/theme-generator.component.html +146 -116
  143. package/src/lib/components/theme-generator/theme-generator.component.ts +44 -24
  144. package/src/lib/components/toggle-radio/shared/toggle-options.types.ts +8 -0
  145. package/src/lib/components/toggle-radio/shared/toggle-options.util.ts +44 -0
  146. package/src/lib/components/toggle-radio/toggle-radio/toggle-radio.component.css +135 -0
  147. package/src/lib/components/toggle-radio/toggle-radio/toggle-radio.component.html +52 -0
  148. package/src/lib/components/toggle-radio/toggle-radio/toggle-radio.component.ts +198 -0
  149. package/src/lib/components/toggle-radio/toggle-radio/toggle-radio.types.ts +1 -0
  150. package/src/lib/components/toggle-radio/toggle-segment/segment-toggle.component.css +108 -0
  151. package/src/lib/components/toggle-radio/toggle-segment/segment-toggle.component.html +37 -0
  152. package/src/lib/components/toggle-radio/toggle-segment/segment-toggle.component.ts +193 -0
  153. package/src/lib/components/toggle-radio/toggle-segment/segment-toggle.types.ts +1 -0
  154. package/src/lib/components/tooltip/tooltip.directive.ts +12 -9
  155. package/src/lib/components/tooltip/tooltip.service.ts +331 -133
  156. package/src/lib/components/tooltip/tooltip.types.ts +9 -0
  157. package/src/lib/components/viewer/viewer-image/image-viewer.component.css +90 -4
  158. package/src/lib/components/viewer/viewer-image/image-viewer.component.html +52 -103
  159. package/src/lib/components/viewer/viewer-image/image-viewer.component.ts +182 -177
  160. package/src/lib/components/viewer/viewer-image/image-viewer.types.ts +3 -0
  161. package/src/lib/components/viewer/viewer-pdf/pdf-viewer.component.css +177 -0
  162. package/src/lib/components/viewer/viewer-pdf/pdf-viewer.component.html +74 -24
  163. package/src/lib/components/viewer/viewer-pdf/pdf-viewer.component.ts +168 -15
  164. package/src/lib/components/viewer/viewer-pdf/pdf-viewer.types.ts +1 -0
  165. package/src/styles.css +2 -2
  166. package/lib/services/static/icons.service.d.ts +0 -65
  167. package/src/lib/components/colors-config/README.md +0 -38
  168. package/src/lib/components/form/form-sidebar/sidebar-form.component.scss +0 -0
  169. package/src/lib/components/form/form-validation/validation-form.component.scss +0 -0
  170. package/src/lib/components/menu/menu-options-table/options-table-menu.component.scss +0 -0
  171. package/src/lib/components/menu/options-coach-menu/options-coach-menu.component.scss +0 -12
  172. package/src/lib/components/sidebar/sidebar-static/static-sidebar.component.scss +0 -0
  173. package/src/lib/components/toggle-radio/toggle-radio.component.html +0 -51
  174. package/src/lib/components/toggle-radio/toggle-radio.component.ts +0 -222
  175. package/src/lib/components/viewer/viewer-pdf/pdf-viewer.component.scss +0 -0
  176. package/tailjng-0.1.6.tgz +0 -0
@@ -1,27 +1,55 @@
1
- import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
2
- import { CommonModule } from '@angular/common';
1
+ import { NgClass } from '@angular/common';
2
+ import {
3
+ Component,
4
+ HostListener,
5
+ Input,
6
+ OnChanges,
7
+ SimpleChanges,
8
+ } from '@angular/core';
3
9
  import { SafeUrl } from '@angular/platform-browser';
4
- import { LucideAngularModule } from 'lucide-angular';
5
- import { JIconsService } from 'tailjng';
10
+ import { Icons } from '../../.config/icons/icons.lucide';
6
11
  import { JButtonComponent } from '../../button/button.component';
12
+ import { JIconComponent } from '../../icon/icon.component';
7
13
  import { JTooltipDirective } from '../../tooltip/tooltip.directive';
8
-
14
+ import { ImageObjectFit, ImageViewerControlSize } from './image-viewer.types';
15
+
16
+ export type { ImageObjectFit, ImageViewerControlSize } from './image-viewer.types';
17
+
18
+ const ZOOM_MIN = 0.5;
19
+ const ZOOM_MAX = 3;
20
+ const ZOOM_STEP = 0.1;
21
+
22
+ const CONTROL_BUTTON_SIZES: Record<ImageViewerControlSize, string> = {
23
+ sm: 'w-[28px] h-[28px]',
24
+ md: 'w-[32px] h-[32px]',
25
+ lg: 'w-[38px] h-[38px]',
26
+ };
27
+
28
+ const CONTROL_ICON_SIZES: Record<ImageViewerControlSize, number> = {
29
+ sm: 15,
30
+ md: 18,
31
+ lg: 21,
32
+ };
33
+
34
+ /**
35
+ * Image viewer with zoom, rotate, pan, download, and fullscreen controls.
36
+ *
37
+ * Install: `npx tailjng add viewer-image`
38
+ */
9
39
  @Component({
10
40
  selector: 'JImageViewer',
11
- imports: [CommonModule, LucideAngularModule, JButtonComponent, JTooltipDirective],
41
+ imports: [NgClass, JButtonComponent, JTooltipDirective, JIconComponent],
12
42
  templateUrl: './image-viewer.component.html',
13
- styleUrl: './image-viewer.component.css'
43
+ styleUrl: './image-viewer.component.css',
14
44
  })
15
45
  export class JImageViewerComponent implements OnChanges {
46
+ readonly Icons = Icons;
16
47
 
17
48
  @Input() src: string | SafeUrl | null = null;
18
- @Input() alt: string = 'Imagen';
19
- @Input() emptyMessage: string = 'La imagen no existe';
20
-
21
- @Input() objectFit: 'contain' | 'cover' | 'fill' | 'none' | 'scale-down' = 'contain';
22
-
49
+ @Input() alt = 'Imagen';
50
+ @Input() emptyMessage = 'La imagen no existe';
51
+ @Input() objectFit: ImageObjectFit = 'contain';
23
52
  @Input() showControls = true;
24
-
25
53
  @Input() showRotateLeft?: boolean;
26
54
  @Input() showRotateRight?: boolean;
27
55
  @Input() showReset?: boolean;
@@ -29,16 +57,16 @@ export class JImageViewerComponent implements OnChanges {
29
57
  @Input() showFullscreen?: boolean;
30
58
  @Input() showZoomIn?: boolean;
31
59
  @Input() showZoomOut?: boolean;
32
-
33
- @Input() controlSize: 'sm' | 'md' | 'lg' = 'md';
60
+ @Input() controlSize: ImageViewerControlSize = 'md';
61
+ @Input() ngClasses: Record<string, boolean> = {};
34
62
 
35
63
  posX = 0;
36
64
  posY = 0;
37
-
38
65
  zoom = 1;
39
66
  rotate = 0;
40
-
41
67
  isFullscreen = false;
68
+ isDownloading = false;
69
+
42
70
  internalLoading = true;
43
71
  animateTransform = true;
44
72
  hasError = false;
@@ -46,98 +74,159 @@ export class JImageViewerComponent implements OnChanges {
46
74
  private dragging = false;
47
75
  private dragStart = { x: 0, y: 0 };
48
76
 
77
+ /**
78
+ * Whether the image is still loading.
79
+ */
49
80
  get loading(): boolean {
50
81
  return this.internalLoading;
51
82
  }
52
83
 
84
+ /**
85
+ * Whether a source URL is provided.
86
+ */
53
87
  get hasImage(): boolean {
54
88
  return !!this.src;
55
89
  }
56
90
 
57
- constructor(public readonly iconsService: JIconsService) { }
58
-
59
- ngOnChanges(changes: SimpleChanges): void {
60
- if (changes['src'] && changes['src'].currentValue !== changes['src'].previousValue) {
61
- this.reset();
91
+ /**
92
+ * Whether zoom out is at the minimum level.
93
+ */
94
+ get isZoomMin(): boolean {
95
+ return this.zoom <= ZOOM_MIN;
96
+ }
62
97
 
63
- this.hasError = false;
64
- this.internalLoading = !!this.src;
65
- }
98
+ /**
99
+ * Whether zoom in is at the maximum level.
100
+ */
101
+ get isZoomMax(): boolean {
102
+ return this.zoom >= ZOOM_MAX;
66
103
  }
67
104
 
105
+ /**
106
+ * CSS transform applied to the image.
107
+ */
108
+ get imageTransform(): string {
109
+ return `scale(${this.zoom}) rotate(${this.rotate}deg) translate(${this.posX}px, ${this.posY}px)`;
110
+ }
68
111
 
69
112
  /**
70
- * Handles the successful loading of the image.
113
+ * Tailwind size classes for toolbar buttons.
71
114
  */
72
- handleLoad() {
73
- this.internalLoading = false;
74
- this.hasError = false;
115
+ get buttonSizeClass(): string {
116
+ return CONTROL_BUTTON_SIZES[this.controlSize];
75
117
  }
76
118
 
119
+ /**
120
+ * Icon size in px for toolbar buttons.
121
+ */
122
+ get buttonIconSize(): number {
123
+ return CONTROL_ICON_SIZES[this.controlSize];
124
+ }
77
125
 
126
+ /**
127
+ * Shared classes for floating control buttons.
128
+ */
129
+ get controlButtonClasses(): string {
130
+ return `secondary rounded-lg shrink-0 opacity-30 hover:opacity-100 transition-opacity duration-200 ${this.buttonSizeClass}`;
131
+ }
78
132
 
79
133
  /**
80
- * Handles the error loading of the image.
134
+ * Class map for the controls toolbar position.
81
135
  */
82
- handleError() {
83
- this.internalLoading = false;
84
- this.hasError = true;
136
+ get controlsPositionClasses(): Record<string, boolean> {
137
+ return {
138
+ 'top-3 right-3': this.isFullscreen,
139
+ 'top-2 right-2': !this.isFullscreen,
140
+ };
85
141
  }
86
142
 
143
+ /**
144
+ * Class map for the image element.
145
+ */
146
+ get imageClasses(): Record<string, boolean> {
147
+ return {
148
+ 'j-image-viewer__image': true,
149
+ 'invisible': this.loading,
150
+ 'transition-transform': this.animateTransform,
151
+ 'duration-200': this.animateTransform,
152
+ 'ease-in-out': this.animateTransform,
153
+ };
154
+ }
87
155
 
156
+ /**
157
+ * Whether at least one toolbar action is visible.
158
+ */
159
+ get hasAnyControlVisible(): boolean {
160
+ return [
161
+ this.showRotateLeft,
162
+ this.showRotateRight,
163
+ this.showReset,
164
+ this.showZoomOut,
165
+ this.showZoomIn,
166
+ this.showDownload,
167
+ this.showFullscreen,
168
+ ].some((value) => this.isControlVisible(value));
169
+ }
88
170
 
89
171
  /**
90
- * Toggles fullscreen mode for the image viewer.
91
- * @param container
172
+ * Fullscreen toggle icon based on current state.
92
173
  */
93
- toggleFullscreen(container: HTMLElement) {
94
- if (!document.fullscreenElement) {
95
- container.requestFullscreen();
96
- this.isFullscreen = true;
97
- } else {
98
- document.exitFullscreen();
99
- this.isFullscreen = false;
174
+ get fullscreenIcon() {
175
+ return this.isFullscreen ? Icons.Minimize2 : Icons.Scan;
176
+ }
177
+
178
+ ngOnChanges(changes: SimpleChanges): void {
179
+ if (changes['src'] && changes['src'].currentValue !== changes['src'].previousValue) {
180
+ this.resetView();
181
+ this.hasError = false;
182
+ this.internalLoading = !!this.src;
100
183
  }
101
184
  }
102
185
 
186
+ @HostListener('document:fullscreenchange')
187
+ onFullscreenChange(): void {
188
+ this.isFullscreen = !!document.fullscreenElement;
189
+ }
103
190
 
104
191
  /**
105
- * Downloads the currently displayed image.
192
+ * Whether a specific toolbar action should render.
106
193
  */
107
- isDownloading = false;
108
-
109
- private getImageUrl(): string {
110
- if (!this.src) return '';
194
+ isControlVisible(value?: boolean): boolean {
195
+ return value ?? this.showControls;
196
+ }
111
197
 
112
- if (typeof this.src === 'string') {
113
- return this.src;
114
- }
198
+ onImageLoad(): void {
199
+ this.internalLoading = false;
200
+ this.hasError = false;
201
+ }
115
202
 
116
- const anySrc = this.src as any;
203
+ onImageError(): void {
204
+ this.internalLoading = false;
205
+ this.hasError = true;
206
+ }
117
207
 
118
- if (anySrc.changingThisBreaksApplicationSecurity) {
119
- return anySrc.changingThisBreaksApplicationSecurity as string;
208
+ toggleFullscreen(container: HTMLElement): void {
209
+ if (!document.fullscreenElement) {
210
+ container.requestFullscreen();
211
+ this.isFullscreen = true;
212
+ return;
120
213
  }
121
214
 
122
- return this.src.toString();
215
+ document.exitFullscreen();
216
+ this.isFullscreen = false;
123
217
  }
124
218
 
125
-
126
-
127
- /**
128
- * Downloads the currently displayed image, handling both base64 and normal URLs.
129
- * @returns
130
- */
131
- download() {
219
+ download(): void {
132
220
  try {
133
- if (!this.src) return;
221
+ if (!this.src) {
222
+ return;
223
+ }
134
224
 
135
225
  this.isDownloading = true;
136
226
 
137
- const imageUrl = this.getImageUrl();
227
+ const imageUrl = this.resolveImageUrl();
138
228
  const fileName = this.alt?.replace(/\s+/g, '') || 'imagen';
139
229
 
140
- // Si es base64
141
230
  if (imageUrl.startsWith('data:image')) {
142
231
  const link = document.createElement('a');
143
232
  link.href = imageUrl;
@@ -149,7 +238,6 @@ export class JImageViewerComponent implements OnChanges {
149
238
  return;
150
239
  }
151
240
 
152
- // Si es URL normal
153
241
  fetch(imageUrl)
154
242
  .then((response) => {
155
243
  if (!response.ok) {
@@ -175,172 +263,89 @@ export class JImageViewerComponent implements OnChanges {
175
263
  .finally(() => {
176
264
  this.isDownloading = false;
177
265
  });
178
-
179
266
  } catch (err) {
180
267
  this.isDownloading = false;
181
268
  console.error('Error general en download():', err);
182
269
  }
183
270
  }
184
271
 
185
-
186
-
187
-
188
- /**
189
- * Zooms in the image.
190
- */
191
- zoomIn() {
192
- this.zoom = Math.min(this.zoom + 0.1, 3);
272
+ zoomIn(): void {
273
+ this.zoom = Math.min(this.zoom + ZOOM_STEP, ZOOM_MAX);
193
274
  }
194
275
 
195
-
196
-
197
- /**
198
- * Zooms out the image.
199
- */
200
- zoomOut() {
201
- this.zoom = Math.max(this.zoom - 0.1, 0.5);
276
+ zoomOut(): void {
277
+ this.zoom = Math.max(this.zoom - ZOOM_STEP, ZOOM_MIN);
202
278
  }
203
279
 
204
-
205
-
206
- /**
207
- * Rotates the image to the right.
208
- */
209
- rotateRightImg() {
280
+ rotateRight(): void {
210
281
  this.rotate += 90;
211
282
  }
212
283
 
213
-
214
-
215
- /**
216
- * Rotates the image to the left.
217
- */
218
- rotateLeftImg() {
284
+ rotateLeft(): void {
219
285
  this.rotate -= 90;
220
286
  }
221
287
 
222
-
223
-
224
- /**
225
- * Resets the image viewer to its initial state.
226
- */
227
- reset() {
288
+ resetView(): void {
228
289
  this.zoom = 1;
229
290
  this.rotate = 0;
230
291
  this.posX = 0;
231
292
  this.posY = 0;
232
293
  }
233
294
 
295
+ startDrag(event: MouseEvent): void {
296
+ if (this.zoom <= 1) {
297
+ return;
298
+ }
234
299
 
235
-
236
- /**
237
- * Starts dragging the image.
238
- * @param event The mouse event.
239
- * @returns
240
- */
241
- startDrag(event: MouseEvent) {
242
- if (this.zoom <= 1) return;
243
300
  this.dragging = true;
244
301
  this.animateTransform = false;
245
-
246
302
  this.dragStart = {
247
303
  x: event.clientX,
248
- y: event.clientY
304
+ y: event.clientY,
249
305
  };
250
306
 
251
307
  document.addEventListener('mousemove', this.onDrag);
252
308
  document.addEventListener('mouseup', this.endDrag);
253
309
  }
254
310
 
255
-
256
-
257
- /**
258
- * Handles the dragging of the image.
259
- * @param event The mouse event.
260
- * @returns
261
- */
262
- onDrag = (event: MouseEvent) => {
263
- if (!this.dragging) return;
311
+ private onDrag = (event: MouseEvent): void => {
312
+ if (!this.dragging) {
313
+ return;
314
+ }
264
315
 
265
316
  const deltaX = event.clientX - this.dragStart.x;
266
317
  const deltaY = event.clientY - this.dragStart.y;
267
-
268
318
  const angleRad = (this.rotate % 360) * (Math.PI / 180);
269
-
270
319
  const rotatedX = deltaX * Math.cos(angleRad) + deltaY * Math.sin(angleRad);
271
320
  const rotatedY = deltaY * Math.cos(angleRad) - deltaX * Math.sin(angleRad);
272
321
 
273
322
  this.posX += rotatedX;
274
323
  this.posY += rotatedY;
275
-
276
324
  this.dragStart.x = event.clientX;
277
325
  this.dragStart.y = event.clientY;
278
326
  };
279
327
 
280
-
281
- /**
282
- * Ends dragging the image.
283
- */
284
- endDrag = () => {
328
+ private endDrag = (): void => {
285
329
  this.dragging = false;
286
330
  this.animateTransform = true;
287
331
  document.removeEventListener('mousemove', this.onDrag);
288
332
  document.removeEventListener('mouseup', this.endDrag);
289
333
  };
290
334
 
335
+ private resolveImageUrl(): string {
336
+ if (!this.src) {
337
+ return '';
338
+ }
291
339
 
340
+ if (typeof this.src === 'string') {
341
+ return this.src;
342
+ }
292
343
 
293
- /**
294
- * Returns the CSS class for the control buttons based on the selected size.
295
- */
296
- get buttonSizeClass(): string {
297
- const sizes = {
298
- sm: 'w-[28px] h-[28px]',
299
- md: 'w-[32px] h-[32px]',
300
- lg: 'w-[38px] h-[38px]',
301
- };
302
-
303
- return sizes[this.controlSize];
304
- }
305
-
306
-
307
- /**
308
- * Returns the icon size for the control buttons based on the selected size.
309
- */
310
- get buttonIconSize(): number {
311
- const sizes = {
312
- sm: 15,
313
- md: 18,
314
- lg: 21,
315
- };
316
-
317
- return sizes[this.controlSize];
318
- }
319
-
320
-
321
- /**
322
- * Determines if a control button should be visible based on the input value and the general showControls flag.
323
- * @param value The specific control visibility input value.
324
- * @returns True if the control should be visible, false otherwise.
325
- */
326
- isControlVisible(value?: boolean): boolean {
327
- return value ?? this.showControls;
328
- }
329
-
344
+ const safe = this.src as SafeUrl & { changingThisBreaksApplicationSecurity?: string };
345
+ if (safe.changingThisBreaksApplicationSecurity) {
346
+ return safe.changingThisBreaksApplicationSecurity;
347
+ }
330
348
 
331
- /**
332
- * Determines if any control button is visible, used to conditionally display the controls container.
333
- * @returns True if at least one control is visible, false otherwise.
334
- */
335
- get hasAnyControlVisible(): boolean {
336
- return [
337
- this.showRotateLeft,
338
- this.showRotateRight,
339
- this.showReset,
340
- this.showZoomOut,
341
- this.showZoomIn,
342
- this.showDownload,
343
- this.showFullscreen,
344
- ].some(value => this.isControlVisible(value));
349
+ return this.src.toString();
345
350
  }
346
351
  }
@@ -0,0 +1,3 @@
1
+ export type ImageObjectFit = 'contain' | 'cover' | 'fill' | 'none' | 'scale-down';
2
+
3
+ export type ImageViewerControlSize = 'sm' | 'md' | 'lg';
@@ -0,0 +1,177 @@
1
+ :host {
2
+ display: block;
3
+ width: 100%;
4
+ height: 100%;
5
+ }
6
+
7
+ .j-pdf-viewer {
8
+ display: flex;
9
+ flex-direction: column;
10
+ width: 100%;
11
+ height: 100%;
12
+ min-height: 18.75rem;
13
+ }
14
+
15
+ .j-pdf-viewer__frame {
16
+ display: block;
17
+ width: 100%;
18
+ height: 100%;
19
+ min-height: 18.75rem;
20
+ border: 1px solid var(--color-border);
21
+ border-radius: 0.75rem;
22
+ background-color: var(--color-background);
23
+ }
24
+
25
+ :host-context(.dark) .j-pdf-viewer__frame,
26
+ :host-context(html.dark) .j-pdf-viewer__frame {
27
+ border-color: var(--color-dark-border);
28
+ background-color: var(--color-dark-background);
29
+ }
30
+
31
+ .j-pdf-viewer__frame--mobile {
32
+ min-height: 60vh;
33
+ border-radius: 0.75rem 0.75rem 0 0;
34
+ }
35
+
36
+ .j-pdf-viewer__fallback,
37
+ .j-pdf-viewer__empty {
38
+ display: flex;
39
+ flex-direction: column;
40
+ align-items: center;
41
+ justify-content: center;
42
+ gap: 0.75rem;
43
+ padding: 1.5rem;
44
+ text-align: center;
45
+ border: 1px dashed var(--color-border);
46
+ border-radius: 0.75rem;
47
+ min-height: 18.75rem;
48
+ }
49
+
50
+ :host-context(.dark) .j-pdf-viewer__fallback,
51
+ :host-context(.dark) .j-pdf-viewer__empty,
52
+ :host-context(html.dark) .j-pdf-viewer__fallback,
53
+ :host-context(html.dark) .j-pdf-viewer__empty {
54
+ border-color: var(--color-dark-border);
55
+ }
56
+
57
+ .j-pdf-viewer__fallback {
58
+ display: none;
59
+ }
60
+
61
+ .j-pdf-viewer__fallback-title,
62
+ .j-pdf-viewer__mobile-title {
63
+ margin: 0;
64
+ font-size: 0.9375rem;
65
+ font-weight: 600;
66
+ color: var(--color-foreground);
67
+ }
68
+
69
+ :host-context(.dark) .j-pdf-viewer__fallback-title,
70
+ :host-context(.dark) .j-pdf-viewer__mobile-title,
71
+ :host-context(html.dark) .j-pdf-viewer__fallback-title,
72
+ :host-context(html.dark) .j-pdf-viewer__mobile-title {
73
+ color: var(--color-dark-foreground);
74
+ }
75
+
76
+ .j-pdf-viewer__fallback-text,
77
+ .j-pdf-viewer__mobile-text,
78
+ .j-pdf-viewer__message {
79
+ margin: 0;
80
+ font-size: 0.8125rem;
81
+ line-height: 1.45;
82
+ color: var(--color-muted-foreground);
83
+ max-width: 20rem;
84
+ }
85
+
86
+ :host-context(.dark) .j-pdf-viewer__fallback-text,
87
+ :host-context(.dark) .j-pdf-viewer__mobile-text,
88
+ :host-context(.dark) .j-pdf-viewer__message,
89
+ :host-context(html.dark) .j-pdf-viewer__fallback-text,
90
+ :host-context(html.dark) .j-pdf-viewer__mobile-text,
91
+ :host-context(html.dark) .j-pdf-viewer__message {
92
+ color: var(--color-dark-muted-foreground);
93
+ }
94
+
95
+ .j-pdf-viewer__actions {
96
+ display: flex;
97
+ flex-wrap: wrap;
98
+ align-items: center;
99
+ justify-content: center;
100
+ gap: 0.5rem;
101
+ }
102
+
103
+ .j-pdf-viewer__actions--stacked {
104
+ flex-direction: column;
105
+ width: 100%;
106
+ max-width: 16rem;
107
+ }
108
+
109
+ .j-pdf-viewer__mobile {
110
+ display: flex;
111
+ flex-direction: column;
112
+ gap: 0;
113
+ width: 100%;
114
+ height: 100%;
115
+ }
116
+
117
+ .j-pdf-viewer__mobile-card {
118
+ display: flex;
119
+ flex: 1;
120
+ flex-direction: column;
121
+ align-items: center;
122
+ justify-content: center;
123
+ gap: 0.875rem;
124
+ padding: 1.25rem;
125
+ text-align: center;
126
+ border: 1px solid var(--color-border);
127
+ border-radius: 0.75rem;
128
+ background: linear-gradient(
129
+ 180deg,
130
+ color-mix(in srgb, var(--color-muted) 22%, var(--color-background)) 0%,
131
+ var(--color-background) 100%
132
+ );
133
+ }
134
+
135
+ :host-context(.dark) .j-pdf-viewer__mobile-card,
136
+ :host-context(html.dark) .j-pdf-viewer__mobile-card {
137
+ border-color: var(--color-dark-border);
138
+ background: linear-gradient(
139
+ 180deg,
140
+ color-mix(in srgb, var(--color-dark-muted) 30%, var(--color-dark-background)) 0%,
141
+ var(--color-dark-background) 100%
142
+ );
143
+ }
144
+
145
+ .j-pdf-viewer__mobile-copy {
146
+ display: flex;
147
+ flex-direction: column;
148
+ gap: 0.375rem;
149
+ }
150
+
151
+ .j-pdf-viewer__mobile-bar {
152
+ display: flex;
153
+ align-items: center;
154
+ justify-content: center;
155
+ gap: 0.5rem;
156
+ padding: 0.625rem 0.75rem;
157
+ border: 1px solid var(--color-border);
158
+ border-top: 0;
159
+ border-radius: 0 0 0.75rem 0.75rem;
160
+ background-color: var(--color-background);
161
+ }
162
+
163
+ :host-context(.dark) .j-pdf-viewer__mobile-bar,
164
+ :host-context(html.dark) .j-pdf-viewer__mobile-bar {
165
+ border-color: var(--color-dark-border);
166
+ background-color: var(--color-dark-background);
167
+ }
168
+
169
+ @media (max-width: 767px) {
170
+ .j-pdf-viewer {
171
+ min-height: 14rem;
172
+ }
173
+
174
+ .j-pdf-viewer__frame {
175
+ min-height: 14rem;
176
+ }
177
+ }