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.
- package/fesm2022/ng-comps.mjs +4254 -0
- package/package.json +54 -58
- package/src/styles.css +54 -0
- package/types/ng-comps.d.ts +1348 -0
- package/.editorconfig +0 -17
- package/.github/copilot-instructions.md +0 -55
- package/.github/workflows/ci.yml +0 -29
- package/.prettierrc +0 -12
- package/.storybook/main.ts +0 -21
- package/.storybook/preview.ts +0 -27
- package/.storybook/tsconfig.doc.json +0 -10
- package/.storybook/tsconfig.json +0 -15
- package/.storybook/typings.d.ts +0 -4
- package/.vscode/extensions.json +0 -4
- package/.vscode/launch.json +0 -20
- package/.vscode/mcp.json +0 -9
- package/.vscode/tasks.json +0 -42
- package/ACCESSIBILITY.md +0 -127
- package/angular.json +0 -106
- package/documentation.json +0 -13394
- package/ng-package.json +0 -27
- package/public/favicon.ico +0 -0
- package/scripts/prepare-package.mjs +0 -80
- package/src/app/a11y/accessibility.utils.ts +0 -35
- package/src/app/a11y/index.ts +0 -6
- package/src/app/accessibility/ng-comps.a11y.spec.ts +0 -108
- package/src/app/app.config.ts +0 -11
- package/src/app/app.css +0 -107
- package/src/app/app.html +0 -48
- package/src/app/app.routes.ts +0 -3
- package/src/app/app.spec.ts +0 -23
- package/src/app/app.ts +0 -10
- package/src/app/components/accordion/index.ts +0 -2
- package/src/app/components/accordion/mf-accordion.component.css +0 -38
- package/src/app/components/accordion/mf-accordion.component.spec.ts +0 -48
- package/src/app/components/accordion/mf-accordion.component.ts +0 -53
- package/src/app/components/alert/index.ts +0 -2
- package/src/app/components/alert/mf-alert.component.css +0 -100
- package/src/app/components/alert/mf-alert.component.spec.ts +0 -59
- package/src/app/components/alert/mf-alert.component.ts +0 -68
- package/src/app/components/autocomplete/index.ts +0 -5
- package/src/app/components/autocomplete/mf-autocomplete.component.css +0 -105
- package/src/app/components/autocomplete/mf-autocomplete.component.spec.ts +0 -116
- package/src/app/components/autocomplete/mf-autocomplete.component.ts +0 -307
- package/src/app/components/avatar/index.ts +0 -2
- package/src/app/components/avatar/mf-avatar.component.css +0 -27
- package/src/app/components/avatar/mf-avatar.component.spec.ts +0 -49
- package/src/app/components/avatar/mf-avatar.component.ts +0 -99
- package/src/app/components/badge/index.ts +0 -2
- package/src/app/components/badge/mf-badge.component.css +0 -32
- package/src/app/components/badge/mf-badge.component.spec.ts +0 -40
- package/src/app/components/badge/mf-badge.component.ts +0 -105
- package/src/app/components/breadcrumb/index.ts +0 -2
- package/src/app/components/breadcrumb/mf-breadcrumb.component.css +0 -61
- package/src/app/components/breadcrumb/mf-breadcrumb.component.spec.ts +0 -61
- package/src/app/components/breadcrumb/mf-breadcrumb.component.ts +0 -75
- package/src/app/components/button/index.ts +0 -2
- package/src/app/components/button/mf-button.component.css +0 -136
- package/src/app/components/button/mf-button.component.ts +0 -174
- package/src/app/components/card/index.ts +0 -2
- package/src/app/components/card/mf-card.component.css +0 -82
- package/src/app/components/card/mf-card.component.ts +0 -59
- package/src/app/components/checkbox/index.ts +0 -1
- package/src/app/components/checkbox/mf-checkbox.component.css +0 -75
- package/src/app/components/checkbox/mf-checkbox.component.ts +0 -187
- package/src/app/components/chip/index.ts +0 -2
- package/src/app/components/chip/mf-chip.component.css +0 -69
- package/src/app/components/chip/mf-chip.component.spec.ts +0 -47
- package/src/app/components/chip/mf-chip.component.ts +0 -77
- package/src/app/components/datepicker/index.ts +0 -2
- package/src/app/components/datepicker/mf-datepicker.component.css +0 -102
- package/src/app/components/datepicker/mf-datepicker.component.spec.ts +0 -69
- package/src/app/components/datepicker/mf-datepicker.component.ts +0 -233
- package/src/app/components/dialog/index.ts +0 -3
- package/src/app/components/dialog/mf-dialog.component.css +0 -73
- package/src/app/components/dialog/mf-dialog.component.ts +0 -160
- package/src/app/components/dialog/mf-dialog.service.spec.ts +0 -61
- package/src/app/components/dialog/mf-dialog.service.ts +0 -52
- package/src/app/components/divider/index.ts +0 -2
- package/src/app/components/divider/mf-divider.component.css +0 -38
- package/src/app/components/divider/mf-divider.component.spec.ts +0 -40
- package/src/app/components/divider/mf-divider.component.ts +0 -44
- package/src/app/components/form-field/index.ts +0 -1
- package/src/app/components/form-field/mf-form-field.component.css +0 -51
- package/src/app/components/form-field/mf-form-field.component.ts +0 -74
- package/src/app/components/grid-list/index.ts +0 -2
- package/src/app/components/grid-list/mf-grid-list.component.css +0 -47
- package/src/app/components/grid-list/mf-grid-list.component.spec.ts +0 -57
- package/src/app/components/grid-list/mf-grid-list.component.ts +0 -68
- package/src/app/components/icon/index.ts +0 -2
- package/src/app/components/icon/mf-icon.component.css +0 -56
- package/src/app/components/icon/mf-icon.component.ts +0 -41
- package/src/app/components/input/index.ts +0 -2
- package/src/app/components/input/mf-input.component.css +0 -105
- package/src/app/components/input/mf-input.component.ts +0 -217
- package/src/app/components/menu/index.ts +0 -2
- package/src/app/components/menu/mf-menu.component.css +0 -31
- package/src/app/components/menu/mf-menu.component.spec.ts +0 -49
- package/src/app/components/menu/mf-menu.component.ts +0 -66
- package/src/app/components/paginator/index.ts +0 -1
- package/src/app/components/paginator/mf-paginator.component.css +0 -32
- package/src/app/components/paginator/mf-paginator.component.spec.ts +0 -44
- package/src/app/components/paginator/mf-paginator.component.ts +0 -52
- package/src/app/components/progress-bar/index.ts +0 -2
- package/src/app/components/progress-bar/mf-progress-bar.component.css +0 -53
- package/src/app/components/progress-bar/mf-progress-bar.component.spec.ts +0 -65
- package/src/app/components/progress-bar/mf-progress-bar.component.ts +0 -79
- package/src/app/components/progress-spinner/index.ts +0 -2
- package/src/app/components/progress-spinner/mf-progress-spinner.component.css +0 -38
- package/src/app/components/progress-spinner/mf-progress-spinner.component.spec.ts +0 -59
- package/src/app/components/progress-spinner/mf-progress-spinner.component.ts +0 -81
- package/src/app/components/radio-button/index.ts +0 -2
- package/src/app/components/radio-button/mf-radio-button.component.css +0 -86
- package/src/app/components/radio-button/mf-radio-button.component.spec.ts +0 -55
- package/src/app/components/radio-button/mf-radio-button.component.ts +0 -219
- package/src/app/components/select/index.ts +0 -2
- package/src/app/components/select/mf-select.component.css +0 -121
- package/src/app/components/select/mf-select.component.spec.ts +0 -108
- package/src/app/components/select/mf-select.component.ts +0 -252
- package/src/app/components/sidenav/index.ts +0 -2
- package/src/app/components/sidenav/mf-sidenav.component.css +0 -168
- package/src/app/components/sidenav/mf-sidenav.component.spec.ts +0 -57
- package/src/app/components/sidenav/mf-sidenav.component.ts +0 -126
- package/src/app/components/slide-toggle/index.ts +0 -1
- package/src/app/components/slide-toggle/mf-slide-toggle.component.css +0 -42
- package/src/app/components/slide-toggle/mf-slide-toggle.component.spec.ts +0 -43
- package/src/app/components/slide-toggle/mf-slide-toggle.component.ts +0 -188
- package/src/app/components/snackbar/index.ts +0 -2
- package/src/app/components/snackbar/mf-snackbar.service.css +0 -31
- package/src/app/components/snackbar/mf-snackbar.service.spec.ts +0 -81
- package/src/app/components/snackbar/mf-snackbar.service.ts +0 -77
- package/src/app/components/table/index.ts +0 -2
- package/src/app/components/table/mf-table.component.css +0 -68
- package/src/app/components/table/mf-table.component.spec.ts +0 -76
- package/src/app/components/table/mf-table.component.ts +0 -117
- package/src/app/components/tabs/index.ts +0 -2
- package/src/app/components/tabs/mf-tabs.component.css +0 -31
- package/src/app/components/tabs/mf-tabs.component.spec.ts +0 -50
- package/src/app/components/tabs/mf-tabs.component.ts +0 -62
- package/src/app/components/textarea/index.ts +0 -2
- package/src/app/components/textarea/mf-textarea.component.css +0 -48
- package/src/app/components/textarea/mf-textarea.component.spec.ts +0 -55
- package/src/app/components/textarea/mf-textarea.component.ts +0 -227
- package/src/app/components/toolbar/index.ts +0 -2
- package/src/app/components/toolbar/mf-toolbar.component.css +0 -77
- package/src/app/components/toolbar/mf-toolbar.component.ts +0 -56
- package/src/app/components/tooltip/index.ts +0 -3
- package/src/app/components/tooltip/mf-tooltip.component.css +0 -7
- package/src/app/components/tooltip/mf-tooltip.component.spec.ts +0 -37
- package/src/app/components/tooltip/mf-tooltip.component.ts +0 -47
- package/src/app/components/tooltip/mf-tooltip.directive.ts +0 -22
- package/src/index.html +0 -18
- package/src/main.ts +0 -6
- package/src/public-api.ts +0 -31
- package/src/stories/About.mdx +0 -72
- package/src/stories/Accessibility.mdx +0 -59
- package/src/stories/Welcome.mdx +0 -26
- package/src/stories/assets/accessibility.png +0 -0
- package/src/stories/assets/accessibility.svg +0 -1
- package/src/stories/assets/addon-library.png +0 -0
- package/src/stories/assets/assets.png +0 -0
- package/src/stories/assets/avif-test-image.avif +0 -0
- package/src/stories/assets/context.png +0 -0
- package/src/stories/assets/discord.svg +0 -1
- package/src/stories/assets/docs.png +0 -0
- package/src/stories/assets/figma-plugin.png +0 -0
- package/src/stories/assets/github.svg +0 -1
- package/src/stories/assets/share.png +0 -0
- package/src/stories/assets/styling.png +0 -0
- package/src/stories/assets/testing.png +0 -0
- package/src/stories/assets/theming.png +0 -0
- package/src/stories/assets/tutorials.svg +0 -1
- package/src/stories/assets/youtube.svg +0 -1
- package/src/stories/mf-a11y-contracts.stories.ts +0 -472
- package/src/stories/mf-autocomplete.stories.ts +0 -194
- package/src/stories/mf-button.stories.ts +0 -152
- package/src/stories/mf-card.stories.ts +0 -147
- package/src/stories/mf-checkbox.stories.ts +0 -88
- package/src/stories/mf-datepicker.stories.ts +0 -118
- package/src/stories/mf-dialog.stories.ts +0 -159
- package/src/stories/mf-form-field.stories.ts +0 -108
- package/src/stories/mf-grid-list.stories.ts +0 -104
- package/src/stories/mf-icon.stories.ts +0 -133
- package/src/stories/mf-input.stories.ts +0 -158
- package/src/stories/mf-menu.stories.ts +0 -71
- package/src/stories/mf-progress-bar.stories.ts +0 -119
- package/src/stories/mf-progress-spinner.stories.ts +0 -124
- package/src/stories/mf-radio-button.stories.ts +0 -111
- package/src/stories/mf-select.stories.ts +0 -184
- package/src/stories/mf-sidenav.stories.ts +0 -331
- package/src/stories/mf-table.stories.ts +0 -80
- package/src/stories/mf-toolbar.stories.ts +0 -112
- package/src/stories/user.ts +0 -3
- package/tsconfig.app.json +0 -15
- package/tsconfig.json +0 -33
- package/tsconfig.spec.json +0 -15
- 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,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,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
|
-
});
|