ng-comps 1.0.2 → 2.0.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 (197) hide show
  1. package/fesm2022/ng-comps.mjs +4254 -0
  2. package/package.json +54 -58
  3. package/src/styles.css +54 -0
  4. package/types/ng-comps.d.ts +1348 -0
  5. package/.editorconfig +0 -17
  6. package/.github/copilot-instructions.md +0 -55
  7. package/.github/workflows/ci.yml +0 -29
  8. package/.prettierrc +0 -12
  9. package/.storybook/main.ts +0 -21
  10. package/.storybook/preview.ts +0 -27
  11. package/.storybook/tsconfig.doc.json +0 -10
  12. package/.storybook/tsconfig.json +0 -15
  13. package/.storybook/typings.d.ts +0 -4
  14. package/.vscode/extensions.json +0 -4
  15. package/.vscode/launch.json +0 -20
  16. package/.vscode/mcp.json +0 -9
  17. package/.vscode/tasks.json +0 -42
  18. package/ACCESSIBILITY.md +0 -127
  19. package/angular.json +0 -106
  20. package/documentation.json +0 -13394
  21. package/ng-package.json +0 -27
  22. package/public/favicon.ico +0 -0
  23. package/scripts/prepare-package.mjs +0 -80
  24. package/src/app/a11y/accessibility.utils.ts +0 -35
  25. package/src/app/a11y/index.ts +0 -6
  26. package/src/app/accessibility/ng-comps.a11y.spec.ts +0 -108
  27. package/src/app/app.config.ts +0 -11
  28. package/src/app/app.css +0 -107
  29. package/src/app/app.html +0 -48
  30. package/src/app/app.routes.ts +0 -3
  31. package/src/app/app.spec.ts +0 -23
  32. package/src/app/app.ts +0 -10
  33. package/src/app/components/accordion/index.ts +0 -2
  34. package/src/app/components/accordion/mf-accordion.component.css +0 -38
  35. package/src/app/components/accordion/mf-accordion.component.spec.ts +0 -48
  36. package/src/app/components/accordion/mf-accordion.component.ts +0 -53
  37. package/src/app/components/alert/index.ts +0 -2
  38. package/src/app/components/alert/mf-alert.component.css +0 -100
  39. package/src/app/components/alert/mf-alert.component.spec.ts +0 -59
  40. package/src/app/components/alert/mf-alert.component.ts +0 -68
  41. package/src/app/components/autocomplete/index.ts +0 -5
  42. package/src/app/components/autocomplete/mf-autocomplete.component.css +0 -105
  43. package/src/app/components/autocomplete/mf-autocomplete.component.spec.ts +0 -116
  44. package/src/app/components/autocomplete/mf-autocomplete.component.ts +0 -307
  45. package/src/app/components/avatar/index.ts +0 -2
  46. package/src/app/components/avatar/mf-avatar.component.css +0 -27
  47. package/src/app/components/avatar/mf-avatar.component.spec.ts +0 -49
  48. package/src/app/components/avatar/mf-avatar.component.ts +0 -99
  49. package/src/app/components/badge/index.ts +0 -2
  50. package/src/app/components/badge/mf-badge.component.css +0 -32
  51. package/src/app/components/badge/mf-badge.component.spec.ts +0 -40
  52. package/src/app/components/badge/mf-badge.component.ts +0 -105
  53. package/src/app/components/breadcrumb/index.ts +0 -2
  54. package/src/app/components/breadcrumb/mf-breadcrumb.component.css +0 -61
  55. package/src/app/components/breadcrumb/mf-breadcrumb.component.spec.ts +0 -61
  56. package/src/app/components/breadcrumb/mf-breadcrumb.component.ts +0 -75
  57. package/src/app/components/button/index.ts +0 -2
  58. package/src/app/components/button/mf-button.component.css +0 -136
  59. package/src/app/components/button/mf-button.component.ts +0 -174
  60. package/src/app/components/card/index.ts +0 -2
  61. package/src/app/components/card/mf-card.component.css +0 -82
  62. package/src/app/components/card/mf-card.component.ts +0 -59
  63. package/src/app/components/checkbox/index.ts +0 -1
  64. package/src/app/components/checkbox/mf-checkbox.component.css +0 -75
  65. package/src/app/components/checkbox/mf-checkbox.component.ts +0 -187
  66. package/src/app/components/chip/index.ts +0 -2
  67. package/src/app/components/chip/mf-chip.component.css +0 -69
  68. package/src/app/components/chip/mf-chip.component.spec.ts +0 -47
  69. package/src/app/components/chip/mf-chip.component.ts +0 -77
  70. package/src/app/components/datepicker/index.ts +0 -2
  71. package/src/app/components/datepicker/mf-datepicker.component.css +0 -102
  72. package/src/app/components/datepicker/mf-datepicker.component.spec.ts +0 -69
  73. package/src/app/components/datepicker/mf-datepicker.component.ts +0 -233
  74. package/src/app/components/dialog/index.ts +0 -3
  75. package/src/app/components/dialog/mf-dialog.component.css +0 -73
  76. package/src/app/components/dialog/mf-dialog.component.ts +0 -160
  77. package/src/app/components/dialog/mf-dialog.service.spec.ts +0 -61
  78. package/src/app/components/dialog/mf-dialog.service.ts +0 -52
  79. package/src/app/components/divider/index.ts +0 -2
  80. package/src/app/components/divider/mf-divider.component.css +0 -38
  81. package/src/app/components/divider/mf-divider.component.spec.ts +0 -40
  82. package/src/app/components/divider/mf-divider.component.ts +0 -44
  83. package/src/app/components/form-field/index.ts +0 -1
  84. package/src/app/components/form-field/mf-form-field.component.css +0 -51
  85. package/src/app/components/form-field/mf-form-field.component.ts +0 -74
  86. package/src/app/components/grid-list/index.ts +0 -2
  87. package/src/app/components/grid-list/mf-grid-list.component.css +0 -47
  88. package/src/app/components/grid-list/mf-grid-list.component.spec.ts +0 -57
  89. package/src/app/components/grid-list/mf-grid-list.component.ts +0 -68
  90. package/src/app/components/icon/index.ts +0 -2
  91. package/src/app/components/icon/mf-icon.component.css +0 -56
  92. package/src/app/components/icon/mf-icon.component.ts +0 -41
  93. package/src/app/components/input/index.ts +0 -2
  94. package/src/app/components/input/mf-input.component.css +0 -105
  95. package/src/app/components/input/mf-input.component.ts +0 -217
  96. package/src/app/components/menu/index.ts +0 -2
  97. package/src/app/components/menu/mf-menu.component.css +0 -31
  98. package/src/app/components/menu/mf-menu.component.spec.ts +0 -49
  99. package/src/app/components/menu/mf-menu.component.ts +0 -66
  100. package/src/app/components/paginator/index.ts +0 -1
  101. package/src/app/components/paginator/mf-paginator.component.css +0 -32
  102. package/src/app/components/paginator/mf-paginator.component.spec.ts +0 -44
  103. package/src/app/components/paginator/mf-paginator.component.ts +0 -52
  104. package/src/app/components/progress-bar/index.ts +0 -2
  105. package/src/app/components/progress-bar/mf-progress-bar.component.css +0 -53
  106. package/src/app/components/progress-bar/mf-progress-bar.component.spec.ts +0 -65
  107. package/src/app/components/progress-bar/mf-progress-bar.component.ts +0 -79
  108. package/src/app/components/progress-spinner/index.ts +0 -2
  109. package/src/app/components/progress-spinner/mf-progress-spinner.component.css +0 -38
  110. package/src/app/components/progress-spinner/mf-progress-spinner.component.spec.ts +0 -59
  111. package/src/app/components/progress-spinner/mf-progress-spinner.component.ts +0 -81
  112. package/src/app/components/radio-button/index.ts +0 -2
  113. package/src/app/components/radio-button/mf-radio-button.component.css +0 -86
  114. package/src/app/components/radio-button/mf-radio-button.component.spec.ts +0 -55
  115. package/src/app/components/radio-button/mf-radio-button.component.ts +0 -219
  116. package/src/app/components/select/index.ts +0 -2
  117. package/src/app/components/select/mf-select.component.css +0 -121
  118. package/src/app/components/select/mf-select.component.spec.ts +0 -108
  119. package/src/app/components/select/mf-select.component.ts +0 -252
  120. package/src/app/components/sidenav/index.ts +0 -2
  121. package/src/app/components/sidenav/mf-sidenav.component.css +0 -168
  122. package/src/app/components/sidenav/mf-sidenav.component.spec.ts +0 -57
  123. package/src/app/components/sidenav/mf-sidenav.component.ts +0 -126
  124. package/src/app/components/slide-toggle/index.ts +0 -1
  125. package/src/app/components/slide-toggle/mf-slide-toggle.component.css +0 -42
  126. package/src/app/components/slide-toggle/mf-slide-toggle.component.spec.ts +0 -43
  127. package/src/app/components/slide-toggle/mf-slide-toggle.component.ts +0 -188
  128. package/src/app/components/snackbar/index.ts +0 -2
  129. package/src/app/components/snackbar/mf-snackbar.service.css +0 -31
  130. package/src/app/components/snackbar/mf-snackbar.service.spec.ts +0 -81
  131. package/src/app/components/snackbar/mf-snackbar.service.ts +0 -77
  132. package/src/app/components/table/index.ts +0 -2
  133. package/src/app/components/table/mf-table.component.css +0 -68
  134. package/src/app/components/table/mf-table.component.spec.ts +0 -76
  135. package/src/app/components/table/mf-table.component.ts +0 -117
  136. package/src/app/components/tabs/index.ts +0 -2
  137. package/src/app/components/tabs/mf-tabs.component.css +0 -31
  138. package/src/app/components/tabs/mf-tabs.component.spec.ts +0 -50
  139. package/src/app/components/tabs/mf-tabs.component.ts +0 -62
  140. package/src/app/components/textarea/index.ts +0 -2
  141. package/src/app/components/textarea/mf-textarea.component.css +0 -48
  142. package/src/app/components/textarea/mf-textarea.component.spec.ts +0 -55
  143. package/src/app/components/textarea/mf-textarea.component.ts +0 -227
  144. package/src/app/components/toolbar/index.ts +0 -2
  145. package/src/app/components/toolbar/mf-toolbar.component.css +0 -77
  146. package/src/app/components/toolbar/mf-toolbar.component.ts +0 -56
  147. package/src/app/components/tooltip/index.ts +0 -3
  148. package/src/app/components/tooltip/mf-tooltip.component.css +0 -7
  149. package/src/app/components/tooltip/mf-tooltip.component.spec.ts +0 -37
  150. package/src/app/components/tooltip/mf-tooltip.component.ts +0 -47
  151. package/src/app/components/tooltip/mf-tooltip.directive.ts +0 -22
  152. package/src/index.html +0 -18
  153. package/src/main.ts +0 -6
  154. package/src/public-api.ts +0 -31
  155. package/src/stories/About.mdx +0 -72
  156. package/src/stories/Accessibility.mdx +0 -59
  157. package/src/stories/Welcome.mdx +0 -26
  158. package/src/stories/assets/accessibility.png +0 -0
  159. package/src/stories/assets/accessibility.svg +0 -1
  160. package/src/stories/assets/addon-library.png +0 -0
  161. package/src/stories/assets/assets.png +0 -0
  162. package/src/stories/assets/avif-test-image.avif +0 -0
  163. package/src/stories/assets/context.png +0 -0
  164. package/src/stories/assets/discord.svg +0 -1
  165. package/src/stories/assets/docs.png +0 -0
  166. package/src/stories/assets/figma-plugin.png +0 -0
  167. package/src/stories/assets/github.svg +0 -1
  168. package/src/stories/assets/share.png +0 -0
  169. package/src/stories/assets/styling.png +0 -0
  170. package/src/stories/assets/testing.png +0 -0
  171. package/src/stories/assets/theming.png +0 -0
  172. package/src/stories/assets/tutorials.svg +0 -1
  173. package/src/stories/assets/youtube.svg +0 -1
  174. package/src/stories/mf-a11y-contracts.stories.ts +0 -472
  175. package/src/stories/mf-autocomplete.stories.ts +0 -194
  176. package/src/stories/mf-button.stories.ts +0 -152
  177. package/src/stories/mf-card.stories.ts +0 -147
  178. package/src/stories/mf-checkbox.stories.ts +0 -88
  179. package/src/stories/mf-datepicker.stories.ts +0 -118
  180. package/src/stories/mf-dialog.stories.ts +0 -159
  181. package/src/stories/mf-form-field.stories.ts +0 -108
  182. package/src/stories/mf-grid-list.stories.ts +0 -104
  183. package/src/stories/mf-icon.stories.ts +0 -133
  184. package/src/stories/mf-input.stories.ts +0 -158
  185. package/src/stories/mf-menu.stories.ts +0 -71
  186. package/src/stories/mf-progress-bar.stories.ts +0 -119
  187. package/src/stories/mf-progress-spinner.stories.ts +0 -124
  188. package/src/stories/mf-radio-button.stories.ts +0 -111
  189. package/src/stories/mf-select.stories.ts +0 -184
  190. package/src/stories/mf-sidenav.stories.ts +0 -331
  191. package/src/stories/mf-table.stories.ts +0 -80
  192. package/src/stories/mf-toolbar.stories.ts +0 -112
  193. package/src/stories/user.ts +0 -3
  194. package/tsconfig.app.json +0 -15
  195. package/tsconfig.json +0 -33
  196. package/tsconfig.spec.json +0 -15
  197. package/vercel.json +0 -6
@@ -1,233 +0,0 @@
1
- import {
2
- ChangeDetectionStrategy,
3
- ChangeDetectorRef,
4
- Component,
5
- computed,
6
- effect,
7
- forwardRef,
8
- inject,
9
- input,
10
- output,
11
- signal,
12
- } from '@angular/core';
13
- import {
14
- ControlValueAccessor,
15
- NG_VALUE_ACCESSOR,
16
- NgControl,
17
- } from '@angular/forms';
18
- import { ErrorStateMatcher } from '@angular/material/core';
19
- import { MatNativeDateModule } from '@angular/material/core';
20
- import { MatDatepickerModule } from '@angular/material/datepicker';
21
- import { MatFormFieldModule } from '@angular/material/form-field';
22
- import { MatIconModule } from '@angular/material/icon';
23
- import { MatInputModule } from '@angular/material/input';
24
- import {
25
- createUniqueId,
26
- hasAccessibleName,
27
- mergeAriaIds,
28
- warnInDev,
29
- } from '../../a11y';
30
-
31
- export type MfDatepickerSize = 'sm' | 'md' | 'lg';
32
-
33
- /**
34
- * Selector de fecha de la librería ng-comps.
35
- * Envuelve Angular Material `mat-datepicker` y expone una API uniforme
36
- * con look and feel de marca.
37
- */
38
- @Component({
39
- selector: 'mf-datepicker',
40
- imports: [
41
- MatDatepickerModule,
42
- MatFormFieldModule,
43
- MatInputModule,
44
- MatNativeDateModule,
45
- MatIconModule,
46
- ],
47
- providers: [
48
- {
49
- provide: NG_VALUE_ACCESSOR,
50
- useExisting: forwardRef(() => MfDatepickerComponent),
51
- multi: true,
52
- },
53
- ],
54
- template: `
55
- <mat-form-field [appearance]="'outline'" [class]="hostClasses()">
56
- @if (label()) {
57
- <mat-label>{{ label() }}</mat-label>
58
- }
59
- <input
60
- matInput
61
- [id]="controlId()"
62
- [matDatepicker]="picker"
63
- [placeholder]="placeholder()"
64
- [disabled]="isDisabled()"
65
- [required]="required()"
66
- [min]="min()"
67
- [max]="max()"
68
- [value]="internalValue()"
69
- [errorStateMatcher]="errorStateMatcher"
70
- [attr.aria-label]="resolvedAriaLabel()"
71
- [attr.aria-labelledby]="ariaLabelledby() || null"
72
- [attr.aria-describedby]="describedBy()"
73
- [attr.aria-invalid]="isInvalid() ? 'true' : null"
74
- [attr.aria-required]="required() ? 'true' : null"
75
- (dateChange)="onDateChange($event)"
76
- (blur)="onBlur()"
77
- />
78
- <mat-datepicker-toggle
79
- matIconSuffix
80
- [for]="picker"
81
- [disabled]="isDisabled()"
82
- [attr.aria-label]="toggleAriaLabel()"
83
- >
84
- <mat-icon matDatepickerToggleIcon aria-hidden="true">
85
- calendar_month
86
- </mat-icon>
87
- </mat-datepicker-toggle>
88
- <mat-datepicker #picker />
89
- @if (hint()) {
90
- <mat-hint [attr.id]="hintId()">{{ hint() }}</mat-hint>
91
- }
92
- </mat-form-field>
93
- @if (error()) {
94
- <p class="mf-datepicker__error" [attr.id]="errorId()" role="alert">
95
- {{ error() }}
96
- </p>
97
- }
98
- `,
99
- styleUrl: './mf-datepicker.component.css',
100
- changeDetection: ChangeDetectionStrategy.OnPush,
101
- })
102
- export class MfDatepickerComponent implements ControlValueAccessor {
103
- private readonly cdr = inject(ChangeDetectorRef);
104
- private readonly ngControl = inject(NgControl, { self: true, optional: true });
105
- private readonly generatedId = createUniqueId('mf-datepicker');
106
- private readonly disabledFromForm = signal(false);
107
- protected readonly internalValue = signal<Date | null>(null);
108
- private onControlChange: (value: Date | null) => void = () => undefined;
109
- private onControlTouched: () => void = () => undefined;
110
- readonly errorStateMatcher: ErrorStateMatcher = {
111
- isErrorState: (control) =>
112
- Boolean(
113
- this.error() || (control?.invalid && (control.touched || control.dirty)),
114
- ),
115
- };
116
-
117
- /** ID del control */
118
- readonly id = input<string | undefined>(undefined);
119
- /** Etiqueta flotante del campo */
120
- readonly label = input<string | undefined>(undefined);
121
- /** Etiqueta accesible alternativa cuando no existe label visible */
122
- readonly ariaLabel = input<string | undefined>(undefined);
123
- /** Referencia externa a elementos que etiquetan el control */
124
- readonly ariaLabelledby = input<string | undefined>(undefined);
125
- /** Referencia externa a elementos descriptivos adicionales */
126
- readonly ariaDescribedby = input<string | undefined>(undefined);
127
- /** Placeholder del input */
128
- readonly placeholder = input('DD/MM/YYYY');
129
- /** Tamaño del campo */
130
- readonly size = input<MfDatepickerSize>('md');
131
- /** Deshabilitado */
132
- readonly disabled = input(false);
133
- /** Requerido */
134
- readonly required = input(false);
135
- /** Valor inicial del datepicker */
136
- readonly value = input<Date | null>(null);
137
- /** Texto de ayuda debajo del campo */
138
- readonly hint = input<string | undefined>(undefined);
139
- /** Mensaje de error */
140
- readonly error = input<string | undefined>(undefined);
141
- /** Fecha mínima seleccionable */
142
- readonly min = input<Date | null>(null);
143
- /** Fecha máxima seleccionable */
144
- readonly max = input<Date | null>(null);
145
- /** Ancho completo */
146
- readonly fullWidth = input(false);
147
- /** Etiqueta accesible del botón para abrir el calendario */
148
- readonly toggleAriaLabel = input('Open calendar');
149
-
150
- readonly mfChange = output<Date | null>();
151
- readonly mfBlur = output<void>();
152
-
153
- constructor() {
154
- effect(() => {
155
- this.internalValue.set(this.value());
156
- });
157
-
158
- effect(() => {
159
- if (
160
- !hasAccessibleName(
161
- this.label(),
162
- this.ariaLabel(),
163
- this.ariaLabelledby(),
164
- )
165
- ) {
166
- warnInDev(
167
- 'mf-datepicker requiere `label`, `ariaLabel` o `ariaLabelledby` para exponer un nombre accesible.',
168
- );
169
- }
170
- });
171
- }
172
-
173
- readonly controlId = computed(() => this.id() ?? this.generatedId);
174
- readonly hintId = computed(() =>
175
- this.hint() ? `${this.controlId()}-hint` : null,
176
- );
177
- readonly errorId = computed(() =>
178
- this.error() ? `${this.controlId()}-error` : null,
179
- );
180
- readonly describedBy = computed(() =>
181
- mergeAriaIds(this.ariaDescribedby(), this.hintId(), this.errorId()),
182
- );
183
- readonly resolvedAriaLabel = computed(() =>
184
- this.label() ? null : this.ariaLabel() ?? null,
185
- );
186
- readonly isDisabled = computed(
187
- () => this.disabled() || this.disabledFromForm(),
188
- );
189
-
190
- readonly hostClasses = computed(() => {
191
- const classes = ['mf-datepicker', `mf-datepicker--${this.size()}`];
192
- if (this.fullWidth()) classes.push('mf-datepicker--full');
193
- if (this.error()) classes.push('mf-datepicker--error');
194
- return classes.join(' ');
195
- });
196
-
197
- writeValue(value: Date | null): void {
198
- this.internalValue.set(value);
199
- this.cdr.markForCheck();
200
- }
201
-
202
- registerOnChange(fn: (value: Date | null) => void): void {
203
- this.onControlChange = fn;
204
- }
205
-
206
- registerOnTouched(fn: () => void): void {
207
- this.onControlTouched = fn;
208
- }
209
-
210
- setDisabledState(isDisabled: boolean): void {
211
- this.disabledFromForm.set(isDisabled);
212
- this.cdr.markForCheck();
213
- }
214
-
215
- isInvalid(): boolean {
216
- const control = this.ngControl?.control;
217
- return Boolean(
218
- this.error() || (control?.invalid && (control.touched || control.dirty)),
219
- );
220
- }
221
-
222
- onDateChange(event: { value: Date | null }): void {
223
- this.internalValue.set(event.value);
224
- this.onControlChange(event.value);
225
- this.onControlTouched();
226
- this.mfChange.emit(event.value);
227
- }
228
-
229
- onBlur(): void {
230
- this.onControlTouched();
231
- this.mfBlur.emit();
232
- }
233
- }
@@ -1,3 +0,0 @@
1
- export { MfDialogComponent } from './mf-dialog.component';
2
- export { MfDialogService } from './mf-dialog.service';
3
- export type { MfDialogOpenConfig } from './mf-dialog.service';
@@ -1,73 +0,0 @@
1
- /* ── Base ──────────────────────────────────────────────────────── */
2
- .mf-dialog {
3
- font-family: var(--mf-font-base);
4
- min-width: 360px;
5
- max-width: 560px;
6
- }
7
-
8
- /* ── Header ────────────────────────────────────────────────────── */
9
- .mf-dialog__header {
10
- display: flex;
11
- align-items: center;
12
- justify-content: space-between;
13
- gap: var(--mf-space-3);
14
- padding: var(--mf-space-6) var(--mf-space-6) var(--mf-space-3);
15
- }
16
-
17
- .mf-dialog__header--compact {
18
- padding-bottom: 0;
19
- }
20
-
21
- .mf-dialog__title {
22
- font-family: var(--mf-font-display);
23
- font-size: var(--mf-text-xl);
24
- font-weight: var(--mf-weight-bold);
25
- color: var(--mf-color-on-surface);
26
- margin: 0;
27
- line-height: var(--mf-leading-tight);
28
- }
29
-
30
- /* ── Close button ─────────────────────────────────────────────── */
31
- .mf-dialog__close {
32
- flex-shrink: 0;
33
- color: var(--mf-color-neutral-400);
34
- transition:
35
- background-color var(--mf-duration-fast) var(--mf-ease-standard),
36
- color var(--mf-duration-fast) var(--mf-ease-standard);
37
- }
38
-
39
- .mf-dialog__close:hover {
40
- background-color: var(--mf-color-neutral-100);
41
- color: var(--mf-color-on-surface);
42
- }
43
-
44
- .mf-dialog__close .mat-icon {
45
- font-size: 20px;
46
- width: 20px;
47
- height: 20px;
48
- }
49
-
50
- /* ── Body ──────────────────────────────────────────────────────── */
51
- .mf-dialog__body {
52
- padding: 0 var(--mf-space-6);
53
- }
54
-
55
- .mf-dialog__message {
56
- font-size: var(--mf-text-sm);
57
- color: var(--mf-color-neutral-600);
58
- line-height: var(--mf-leading-normal);
59
- margin: 0;
60
- }
61
-
62
- /* ── Content ───────────────────────────────────────────────────── */
63
- .mf-dialog__content {
64
- padding: var(--mf-space-3) var(--mf-space-6);
65
- }
66
-
67
- .mf-dialog__content:empty {
68
- display: none;
69
- }
70
-
71
- /* ── Actions (footer) ─────────────────────────────────────────── */
72
- /* La regla de gap está en styles.css (global) para que aplique también
73
- al contenido proyectado, que escapa al scope de ViewEncapsulation. */
@@ -1,160 +0,0 @@
1
- import {
2
- ChangeDetectionStrategy,
3
- Component,
4
- computed,
5
- effect,
6
- inject,
7
- input,
8
- output,
9
- } from '@angular/core';
10
- import { MatDialogModule, MatDialogRef } from '@angular/material/dialog';
11
- import { MatIconModule } from '@angular/material/icon';
12
- import {
13
- createUniqueId,
14
- hasAccessibleName,
15
- mergeAriaIds,
16
- warnInDev,
17
- } from '../../a11y';
18
-
19
- /**
20
- * Contenido de diálogo de la librería ng-comps.
21
- * Envuelve las directivas de Angular Material `mat-dialog-*` y expone
22
- * una API uniforme con look and feel de marca.
23
- *
24
- * Uso:
25
- * ```
26
- * dialog.open(MfDialogComponent, {
27
- * data: { title: 'Confirmar', message: '¿Deseas continuar?' }
28
- * });
29
- * ```
30
- */
31
- @Component({
32
- selector: 'mf-dialog',
33
- imports: [MatDialogModule, MatIconModule],
34
- template: `
35
- <div
36
- [class]="hostClasses()"
37
- [attr.role]="role()"
38
- aria-modal="true"
39
- [attr.aria-label]="resolvedAriaLabel()"
40
- [attr.aria-labelledby]="computedLabelledby()"
41
- [attr.aria-describedby]="computedDescribedby()"
42
- >
43
- @if (title()) {
44
- <h2 mat-dialog-title class="mf-dialog__header" [id]="titleId()">
45
- <span class="mf-dialog__title">{{ title() }}</span>
46
- @if (showClose()) {
47
- <button
48
- mat-icon-button
49
- class="mf-dialog__close"
50
- (click)="onClose()"
51
- [attr.aria-label]="closeButtonLabel()"
52
- type="button"
53
- >
54
- <mat-icon aria-hidden="true">close</mat-icon>
55
- </button>
56
- }
57
- </h2>
58
- } @else if (showClose()) {
59
- <div class="mf-dialog__header mf-dialog__header--compact">
60
- <span></span>
61
- <button
62
- mat-icon-button
63
- class="mf-dialog__close"
64
- (click)="onClose()"
65
- [attr.aria-label]="closeButtonLabel()"
66
- type="button"
67
- >
68
- <mat-icon aria-hidden="true">close</mat-icon>
69
- </button>
70
- </div>
71
- }
72
-
73
- <div mat-dialog-content class="mf-dialog__content">
74
- @if (message()) {
75
- <p class="mf-dialog__message" [id]="descriptionId()">{{ message() }}</p>
76
- }
77
-
78
- <ng-content />
79
- </div>
80
-
81
- @if (showActions()) {
82
- <div mat-dialog-actions class="mf-dialog__actions">
83
- <ng-content select="[mfDialogActions]" />
84
- </div>
85
- }
86
- </div>
87
- `,
88
- styleUrl: './mf-dialog.component.css',
89
- changeDetection: ChangeDetectionStrategy.OnPush,
90
- })
91
- export class MfDialogComponent {
92
- private readonly dialogRef = inject(MatDialogRef<MfDialogComponent>, {
93
- optional: true,
94
- });
95
- private readonly generatedId = createUniqueId('mf-dialog');
96
-
97
- /** ID base del diálogo */
98
- readonly id = input<string | undefined>(undefined);
99
- /** Título del diálogo */
100
- readonly title = input<string | undefined>(undefined);
101
- /** Etiqueta accesible alternativa cuando no existe título visible */
102
- readonly ariaLabel = input<string | undefined>(undefined);
103
- /** Referencia externa a elementos que etiquetan el diálogo */
104
- readonly ariaLabelledby = input<string | undefined>(undefined);
105
- /** Referencia externa a elementos descriptivos adicionales */
106
- readonly ariaDescribedby = input<string | undefined>(undefined);
107
- /** Mensaje descriptivo */
108
- readonly message = input<string | undefined>(undefined);
109
- /** Mostrar botón de cerrar */
110
- readonly showClose = input(true);
111
- /** Mostrar área de acciones (footer) */
112
- readonly showActions = input(true);
113
- /** Rol del diálogo */
114
- readonly role = input<'dialog' | 'alertdialog'>('dialog');
115
- /** Etiqueta accesible del botón de cierre */
116
- readonly closeButtonLabel = input('Close dialog');
117
-
118
- readonly mfClose = output<void>();
119
-
120
- constructor() {
121
- effect(() => {
122
- if (
123
- !hasAccessibleName(
124
- this.title(),
125
- this.ariaLabel(),
126
- this.ariaLabelledby(),
127
- )
128
- ) {
129
- warnInDev(
130
- 'mf-dialog requiere `title`, `ariaLabel` o `ariaLabelledby` para exponer un nombre accesible.',
131
- );
132
- }
133
- });
134
- }
135
-
136
- readonly dialogId = computed(() => this.id() ?? this.generatedId);
137
- readonly titleId = computed(() => `${this.dialogId()}-title`);
138
- readonly descriptionId = computed(() => `${this.dialogId()}-description`);
139
- readonly computedLabelledby = computed(() =>
140
- mergeAriaIds(
141
- this.ariaLabelledby(),
142
- this.title() ? this.titleId() : null,
143
- ),
144
- );
145
- readonly computedDescribedby = computed(() =>
146
- mergeAriaIds(
147
- this.ariaDescribedby(),
148
- this.message() ? this.descriptionId() : null,
149
- ),
150
- );
151
- readonly resolvedAriaLabel = computed(() =>
152
- this.title() ? null : this.ariaLabel() ?? null,
153
- );
154
- readonly hostClasses = computed(() => 'mf-dialog');
155
-
156
- onClose(): void {
157
- this.mfClose.emit();
158
- this.dialogRef?.close();
159
- }
160
- }
@@ -1,61 +0,0 @@
1
- import { TestBed } from '@angular/core/testing';
2
- import { MatDialog } from '@angular/material/dialog';
3
- import { MfDialogService } from './mf-dialog.service';
4
-
5
- describe('MfDialogService', () => {
6
- let service: MfDialogService;
7
- let dialogSpy: { open: ReturnType<typeof vi.fn> };
8
-
9
- beforeEach(() => {
10
- dialogSpy = {
11
- open: vi.fn().mockReturnValue({}),
12
- };
13
-
14
- TestBed.configureTestingModule({
15
- providers: [
16
- MfDialogService,
17
- { provide: MatDialog, useValue: dialogSpy },
18
- ],
19
- });
20
-
21
- service = TestBed.inject(MfDialogService);
22
- });
23
-
24
- it('should be created', () => {
25
- expect(service).toBeTruthy();
26
- });
27
-
28
- it('should apply safe defaults for focus, role and panel class', () => {
29
- class DialogContentComponent {}
30
-
31
- service.open(DialogContentComponent, {
32
- ariaLabel: 'Delete project',
33
- });
34
-
35
- expect(dialogSpy.open).toHaveBeenCalledWith(
36
- DialogContentComponent,
37
- expect.objectContaining({
38
- role: 'dialog',
39
- autoFocus: 'first-tabbable',
40
- restoreFocus: true,
41
- ariaLabel: 'Delete project',
42
- panelClass: ['mf-dialog-panel'],
43
- }),
44
- );
45
- });
46
-
47
- it('should merge custom panel classes with the base dialog class', () => {
48
- class DialogContentComponent {}
49
-
50
- service.open(DialogContentComponent, {
51
- panelClass: ['custom-panel', 'danger-panel'],
52
- });
53
-
54
- expect(dialogSpy.open).toHaveBeenCalledWith(
55
- DialogContentComponent,
56
- expect.objectContaining({
57
- panelClass: ['mf-dialog-panel', 'custom-panel', 'danger-panel'],
58
- }),
59
- );
60
- });
61
- });
@@ -1,52 +0,0 @@
1
- import { Injectable, TemplateRef, Type, inject } from '@angular/core';
2
- import {
3
- DialogRole,
4
- MatDialog,
5
- MatDialogConfig,
6
- MatDialogRef,
7
- } from '@angular/material/dialog';
8
-
9
- export interface MfDialogOpenConfig<D = unknown>
10
- extends Omit<
11
- MatDialogConfig<D>,
12
- | 'ariaDescribedBy'
13
- | 'ariaLabel'
14
- | 'ariaLabelledBy'
15
- | 'autoFocus'
16
- | 'panelClass'
17
- | 'restoreFocus'
18
- | 'role'
19
- > {
20
- ariaLabel?: string;
21
- ariaLabelledby?: string;
22
- ariaDescribedby?: string;
23
- autoFocus?: MatDialogConfig<D>['autoFocus'];
24
- panelClass?: string | string[];
25
- restoreFocus?: boolean;
26
- role?: DialogRole;
27
- }
28
-
29
- @Injectable({ providedIn: 'root' })
30
- export class MfDialogService {
31
- private readonly dialog = inject(MatDialog);
32
-
33
- open<T, D = unknown, R = unknown>(
34
- component: Type<T> | TemplateRef<T>,
35
- config: MfDialogOpenConfig<D> = {},
36
- ): MatDialogRef<T, R> {
37
- const panelClass = Array.isArray(config.panelClass)
38
- ? ['mf-dialog-panel', ...config.panelClass]
39
- : ['mf-dialog-panel', ...(config.panelClass ? [config.panelClass] : [])];
40
-
41
- return this.dialog.open(component, {
42
- ...config,
43
- role: config.role ?? 'dialog',
44
- autoFocus: config.autoFocus ?? 'first-tabbable',
45
- restoreFocus: config.restoreFocus ?? true,
46
- ariaLabel: config.ariaLabel,
47
- ariaLabelledBy: config.ariaLabelledby,
48
- ariaDescribedBy: config.ariaDescribedby,
49
- panelClass,
50
- });
51
- }
52
- }
@@ -1,2 +0,0 @@
1
- export { MfDividerComponent } from './mf-divider.component';
2
- export type { MfDividerVariant } from './mf-divider.component';
@@ -1,38 +0,0 @@
1
- :host {
2
- display: block;
3
- }
4
-
5
- :host:has(.mf-divider--vertical) {
6
- display: inline-block;
7
- height: 100%;
8
- }
9
-
10
- .mf-divider .mat-divider {
11
- border-top-color: var(--mf-color-border) !important;
12
- }
13
-
14
- .mf-divider--vertical .mat-divider {
15
- border-left-color: var(--mf-color-border) !important;
16
- }
17
-
18
- /* ── Middle inset ─────────────────────────────────────────────── */
19
- .mf-divider--middle .mat-divider {
20
- margin-left: var(--mf-space-4) !important;
21
- margin-right: var(--mf-space-4) !important;
22
- }
23
-
24
- /* ── Spacing ──────────────────────────────────────────────────── */
25
- .mf-divider--spacing-sm {
26
- padding-top: var(--mf-space-2);
27
- padding-bottom: var(--mf-space-2);
28
- }
29
-
30
- .mf-divider--spacing-md {
31
- padding-top: var(--mf-space-4);
32
- padding-bottom: var(--mf-space-4);
33
- }
34
-
35
- .mf-divider--spacing-lg {
36
- padding-top: var(--mf-space-6);
37
- padding-bottom: var(--mf-space-6);
38
- }
@@ -1,40 +0,0 @@
1
- import { ComponentFixture, TestBed } from '@angular/core/testing';
2
- import { MfDividerComponent } from './mf-divider.component';
3
-
4
- describe('MfDividerComponent', () => {
5
- let fixture: ComponentFixture<MfDividerComponent>;
6
- let component: MfDividerComponent;
7
-
8
- beforeEach(async () => {
9
- await TestBed.configureTestingModule({
10
- imports: [MfDividerComponent],
11
- }).compileComponents();
12
-
13
- fixture = TestBed.createComponent(MfDividerComponent);
14
- component = fixture.componentInstance;
15
- fixture.detectChanges();
16
- });
17
-
18
- it('should create', () => {
19
- expect(component).toBeTruthy();
20
- });
21
-
22
- it('should render mat-divider', () => {
23
- const divider = fixture.nativeElement.querySelector('mat-divider');
24
- expect(divider).toBeTruthy();
25
- });
26
-
27
- it('should apply full variant by default', () => {
28
- expect(component.hostClasses()).toContain('mf-divider--full');
29
- });
30
-
31
- it('should compute inset value for inset variant', () => {
32
- fixture.componentRef.setInput('variant', 'inset');
33
- expect(component.insetValue()).toBe(true);
34
- });
35
-
36
- it('should apply vertical class when vertical', () => {
37
- fixture.componentRef.setInput('vertical', true);
38
- expect(component.hostClasses()).toContain('mf-divider--vertical');
39
- });
40
- });