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,307 +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 { MatAutocompleteModule } from '@angular/material/autocomplete';
|
|
20
|
-
import { MatFormFieldModule } from '@angular/material/form-field';
|
|
21
|
-
import { MatIconModule } from '@angular/material/icon';
|
|
22
|
-
import { MatInputModule } from '@angular/material/input';
|
|
23
|
-
import {
|
|
24
|
-
createUniqueId,
|
|
25
|
-
hasAccessibleName,
|
|
26
|
-
mergeAriaIds,
|
|
27
|
-
warnInDev,
|
|
28
|
-
} from '../../a11y';
|
|
29
|
-
|
|
30
|
-
export interface MfAutocompleteOption {
|
|
31
|
-
value: string | number;
|
|
32
|
-
label: string;
|
|
33
|
-
disabled?: boolean;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export type MfAutocompleteSize = 'sm' | 'md' | 'lg';
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Autocompletar de la librerÃa ng-comps.
|
|
40
|
-
* Envuelve Angular Material `mat-autocomplete` y expone una API uniforme
|
|
41
|
-
* con look and feel de marca.
|
|
42
|
-
*
|
|
43
|
-
* El panel desplegable se estiliza con la clase global `mf-autocomplete-panel`.
|
|
44
|
-
* Puedes añadir clases adicionales con `panelClass`.
|
|
45
|
-
*/
|
|
46
|
-
@Component({
|
|
47
|
-
selector: 'mf-autocomplete',
|
|
48
|
-
imports: [
|
|
49
|
-
MatAutocompleteModule,
|
|
50
|
-
MatFormFieldModule,
|
|
51
|
-
MatIconModule,
|
|
52
|
-
MatInputModule,
|
|
53
|
-
],
|
|
54
|
-
providers: [
|
|
55
|
-
{
|
|
56
|
-
provide: NG_VALUE_ACCESSOR,
|
|
57
|
-
useExisting: forwardRef(() => MfAutocompleteComponent),
|
|
58
|
-
multi: true,
|
|
59
|
-
},
|
|
60
|
-
],
|
|
61
|
-
template: `
|
|
62
|
-
<mat-form-field [appearance]="'outline'" [class]="hostClasses()">
|
|
63
|
-
@if (label()) {
|
|
64
|
-
<mat-label>{{ label() }}</mat-label>
|
|
65
|
-
}
|
|
66
|
-
@if (leadingIcon()) {
|
|
67
|
-
<mat-icon matPrefix aria-hidden="true">{{ leadingIcon() }}</mat-icon>
|
|
68
|
-
}
|
|
69
|
-
<input
|
|
70
|
-
matInput
|
|
71
|
-
[id]="controlId()"
|
|
72
|
-
[placeholder]="placeholder()"
|
|
73
|
-
[disabled]="isDisabled()"
|
|
74
|
-
[required]="required()"
|
|
75
|
-
[matAutocomplete]="auto"
|
|
76
|
-
[value]="internalValue()"
|
|
77
|
-
[errorStateMatcher]="errorStateMatcher"
|
|
78
|
-
[attr.aria-label]="resolvedAriaLabel()"
|
|
79
|
-
[attr.aria-labelledby]="ariaLabelledby() || null"
|
|
80
|
-
[attr.aria-describedby]="describedBy()"
|
|
81
|
-
[attr.aria-invalid]="isInvalid() ? 'true' : null"
|
|
82
|
-
[attr.aria-required]="required() ? 'true' : null"
|
|
83
|
-
(input)="onInputChange($event)"
|
|
84
|
-
(blur)="onBlur()"
|
|
85
|
-
/>
|
|
86
|
-
@if (trailingIcon()) {
|
|
87
|
-
<mat-icon matSuffix aria-hidden="true">{{ trailingIcon() }}</mat-icon>
|
|
88
|
-
}
|
|
89
|
-
@if (hint()) {
|
|
90
|
-
<mat-hint [attr.id]="hintId()">{{ hint() }}</mat-hint>
|
|
91
|
-
}
|
|
92
|
-
<mat-autocomplete
|
|
93
|
-
#auto
|
|
94
|
-
[panelWidth]="panelWidth()"
|
|
95
|
-
[class]="autocompletePanelClasses()"
|
|
96
|
-
(optionSelected)="onOptionSelected($event)"
|
|
97
|
-
>
|
|
98
|
-
@for (option of filteredOptions(); track option.value) {
|
|
99
|
-
<mat-option [value]="option.label" [disabled]="option.disabled ?? false">
|
|
100
|
-
{{ option.label }}
|
|
101
|
-
</mat-option>
|
|
102
|
-
}
|
|
103
|
-
</mat-autocomplete>
|
|
104
|
-
</mat-form-field>
|
|
105
|
-
@if (error()) {
|
|
106
|
-
<p class="mf-autocomplete__error" [attr.id]="errorId()" role="alert">
|
|
107
|
-
{{ error() }}
|
|
108
|
-
</p>
|
|
109
|
-
}
|
|
110
|
-
`,
|
|
111
|
-
styleUrl: './mf-autocomplete.component.css',
|
|
112
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
113
|
-
})
|
|
114
|
-
export class MfAutocompleteComponent implements ControlValueAccessor {
|
|
115
|
-
private readonly cdr = inject(ChangeDetectorRef);
|
|
116
|
-
private readonly ngControl = inject(NgControl, { self: true, optional: true });
|
|
117
|
-
private readonly generatedId = createUniqueId('mf-autocomplete');
|
|
118
|
-
private readonly disabledFromForm = signal(false);
|
|
119
|
-
protected readonly internalValue = signal('');
|
|
120
|
-
private onControlChange: (value: string) => void = () => undefined;
|
|
121
|
-
private onControlTouched: () => void = () => undefined;
|
|
122
|
-
readonly errorStateMatcher: ErrorStateMatcher = {
|
|
123
|
-
isErrorState: (control) =>
|
|
124
|
-
Boolean(
|
|
125
|
-
this.error() || (control?.invalid && (control.touched || control.dirty)),
|
|
126
|
-
),
|
|
127
|
-
};
|
|
128
|
-
|
|
129
|
-
/** Lista completa de opciones */
|
|
130
|
-
readonly options = input.required<MfAutocompleteOption[]>();
|
|
131
|
-
/** ID del control */
|
|
132
|
-
readonly id = input<string | undefined>(undefined);
|
|
133
|
-
/** Etiqueta flotante del campo */
|
|
134
|
-
readonly label = input<string | undefined>(undefined);
|
|
135
|
-
/** Etiqueta accesible alternativa cuando no existe label visible */
|
|
136
|
-
readonly ariaLabel = input<string | undefined>(undefined);
|
|
137
|
-
/** Referencia externa a elementos que etiquetan el control */
|
|
138
|
-
readonly ariaLabelledby = input<string | undefined>(undefined);
|
|
139
|
-
/** Referencia externa a elementos descriptivos adicionales */
|
|
140
|
-
readonly ariaDescribedby = input<string | undefined>(undefined);
|
|
141
|
-
/** Placeholder del input */
|
|
142
|
-
readonly placeholder = input('');
|
|
143
|
-
/** Valor actual (texto en el campo) */
|
|
144
|
-
readonly value = input('');
|
|
145
|
-
/** Deshabilitado */
|
|
146
|
-
readonly disabled = input(false);
|
|
147
|
-
/** Requerido */
|
|
148
|
-
readonly required = input(false);
|
|
149
|
-
/** Tamaño del campo */
|
|
150
|
-
readonly size = input<MfAutocompleteSize>('md');
|
|
151
|
-
/** Ancho completo */
|
|
152
|
-
readonly fullWidth = input(false);
|
|
153
|
-
/** Texto de ayuda debajo del campo */
|
|
154
|
-
readonly hint = input<string | undefined>(undefined);
|
|
155
|
-
/** Mensaje de error */
|
|
156
|
-
readonly error = input<string | undefined>(undefined);
|
|
157
|
-
/** Icono al inicio del campo */
|
|
158
|
-
readonly leadingIcon = input<string | undefined>(undefined);
|
|
159
|
-
/** Icono al final del campo */
|
|
160
|
-
readonly trailingIcon = input<string | undefined>(undefined);
|
|
161
|
-
/**
|
|
162
|
-
* Ancho del panel del autocomplete.
|
|
163
|
-
* Por defecto `''`: el panel toma el mismo ancho que el campo.
|
|
164
|
-
* Acepta cualquier valor CSS válido ('300px', '80vw', etc.) para sobreescribirlo.
|
|
165
|
-
*/
|
|
166
|
-
readonly panelWidth = input<string | number>('');
|
|
167
|
-
/**
|
|
168
|
-
* Clases extra que se añaden al panel del autocomplete (overlay).
|
|
169
|
-
* La clase `mf-autocomplete-panel` siempre está presente.
|
|
170
|
-
*/
|
|
171
|
-
readonly panelClass = input<string | string[]>('');
|
|
172
|
-
|
|
173
|
-
/** Emite el texto escrito en el campo */
|
|
174
|
-
readonly mfInput = output<string>();
|
|
175
|
-
/** Emite el valor de la opción seleccionada */
|
|
176
|
-
readonly mfOptionSelected = output<MfAutocompleteOption>();
|
|
177
|
-
readonly mfBlur = output<void>();
|
|
178
|
-
|
|
179
|
-
constructor() {
|
|
180
|
-
effect(() => {
|
|
181
|
-
this.internalValue.set(this.value());
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
effect(() => {
|
|
185
|
-
if (
|
|
186
|
-
!hasAccessibleName(
|
|
187
|
-
this.label(),
|
|
188
|
-
this.ariaLabel(),
|
|
189
|
-
this.ariaLabelledby(),
|
|
190
|
-
)
|
|
191
|
-
) {
|
|
192
|
-
warnInDev(
|
|
193
|
-
'mf-autocomplete requiere `label`, `ariaLabel` o `ariaLabelledby` para exponer un nombre accesible.',
|
|
194
|
-
);
|
|
195
|
-
}
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
readonly controlId = computed(() => this.id() ?? this.generatedId);
|
|
200
|
-
readonly hintId = computed(() =>
|
|
201
|
-
this.hint() ? `${this.controlId()}-hint` : null,
|
|
202
|
-
);
|
|
203
|
-
readonly errorId = computed(() =>
|
|
204
|
-
this.error() ? `${this.controlId()}-error` : null,
|
|
205
|
-
);
|
|
206
|
-
readonly describedBy = computed(() =>
|
|
207
|
-
mergeAriaIds(this.ariaDescribedby(), this.hintId(), this.errorId()),
|
|
208
|
-
);
|
|
209
|
-
readonly resolvedAriaLabel = computed(() =>
|
|
210
|
-
this.label() ? null : this.ariaLabel() ?? null,
|
|
211
|
-
);
|
|
212
|
-
readonly isDisabled = computed(
|
|
213
|
-
() => this.disabled() || this.disabledFromForm(),
|
|
214
|
-
);
|
|
215
|
-
|
|
216
|
-
readonly hostClasses = computed(() => {
|
|
217
|
-
const classes = ['mf-autocomplete', `mf-autocomplete--${this.size()}`];
|
|
218
|
-
if (this.fullWidth()) classes.push('mf-autocomplete--full');
|
|
219
|
-
if (this.error()) classes.push('mf-autocomplete--error');
|
|
220
|
-
return classes.join(' ');
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
readonly autocompletePanelClasses = computed(() => {
|
|
224
|
-
const base = ['mf-autocomplete-panel'];
|
|
225
|
-
const extra = this.panelClass();
|
|
226
|
-
|
|
227
|
-
if (Array.isArray(extra)) {
|
|
228
|
-
base.push(...extra.filter(Boolean));
|
|
229
|
-
} else if (extra) {
|
|
230
|
-
base.push(extra);
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
return base.join(' ');
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
readonly filteredOptions = computed(() => {
|
|
237
|
-
const query = this.internalValue().toLowerCase().trim();
|
|
238
|
-
if (!query) {
|
|
239
|
-
return this.options();
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
return this.options().filter((option) =>
|
|
243
|
-
option.label.toLowerCase().includes(query),
|
|
244
|
-
);
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
get inputValue(): string {
|
|
248
|
-
return this.internalValue();
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
set inputValue(value: string) {
|
|
252
|
-
this.internalValue.set(value);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
writeValue(value: string | null): void {
|
|
256
|
-
this.internalValue.set(value ?? '');
|
|
257
|
-
this.cdr.markForCheck();
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
registerOnChange(fn: (value: string) => void): void {
|
|
261
|
-
this.onControlChange = fn;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
registerOnTouched(fn: () => void): void {
|
|
265
|
-
this.onControlTouched = fn;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
setDisabledState(isDisabled: boolean): void {
|
|
269
|
-
this.disabledFromForm.set(isDisabled);
|
|
270
|
-
this.cdr.markForCheck();
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
isInvalid(): boolean {
|
|
274
|
-
const control = this.ngControl?.control;
|
|
275
|
-
return Boolean(
|
|
276
|
-
this.error() || (control?.invalid && (control.touched || control.dirty)),
|
|
277
|
-
);
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
onInputChange(event: Event | string): void {
|
|
281
|
-
const value =
|
|
282
|
-
typeof event === 'string'
|
|
283
|
-
? event
|
|
284
|
-
: (event.target as HTMLInputElement).value;
|
|
285
|
-
|
|
286
|
-
this.internalValue.set(value);
|
|
287
|
-
this.onControlChange(value);
|
|
288
|
-
this.mfInput.emit(value);
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
onOptionSelected(event: { option: { value: string } }): void {
|
|
292
|
-
const label = event.option.value;
|
|
293
|
-
const match = this.options().find((option) => option.label === label);
|
|
294
|
-
|
|
295
|
-
if (match) {
|
|
296
|
-
this.internalValue.set(match.label);
|
|
297
|
-
this.onControlChange(match.label);
|
|
298
|
-
this.onControlTouched();
|
|
299
|
-
this.mfOptionSelected.emit(match);
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
onBlur(): void {
|
|
304
|
-
this.onControlTouched();
|
|
305
|
-
this.mfBlur.emit();
|
|
306
|
-
}
|
|
307
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
:host {
|
|
2
|
-
display: inline-block;
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
.mf-avatar {
|
|
6
|
-
display: inline-flex;
|
|
7
|
-
align-items: center;
|
|
8
|
-
justify-content: center;
|
|
9
|
-
overflow: hidden;
|
|
10
|
-
object-fit: cover;
|
|
11
|
-
background-color: var(--mf-color-primary-100);
|
|
12
|
-
color: var(--mf-color-primary-800);
|
|
13
|
-
font-family: var(--mf-font-base);
|
|
14
|
-
font-weight: var(--mf-weight-bold);
|
|
15
|
-
user-select: none;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/* ── Tamaños ───────────────────────────────────────────────────── */
|
|
19
|
-
.mf-avatar--xs { width: 24px; height: 24px; font-size: var(--mf-text-xs); }
|
|
20
|
-
.mf-avatar--sm { width: 32px; height: 32px; font-size: var(--mf-text-sm); }
|
|
21
|
-
.mf-avatar--md { width: 40px; height: 40px; font-size: var(--mf-text-base); }
|
|
22
|
-
.mf-avatar--lg { width: 56px; height: 56px; font-size: var(--mf-text-xl); }
|
|
23
|
-
.mf-avatar--xl { width: 80px; height: 80px; font-size: var(--mf-text-3xl); }
|
|
24
|
-
|
|
25
|
-
/* ── Forma ─────────────────────────────────────────────────────── */
|
|
26
|
-
.mf-avatar--circle { border-radius: var(--mf-radius-full); }
|
|
27
|
-
.mf-avatar--rounded { border-radius: var(--mf-radius-md); }
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
2
|
-
import { MfAvatarComponent } from './mf-avatar.component';
|
|
3
|
-
|
|
4
|
-
describe('MfAvatarComponent', () => {
|
|
5
|
-
let fixture: ComponentFixture<MfAvatarComponent>;
|
|
6
|
-
let component: MfAvatarComponent;
|
|
7
|
-
|
|
8
|
-
beforeEach(async () => {
|
|
9
|
-
await TestBed.configureTestingModule({
|
|
10
|
-
imports: [MfAvatarComponent],
|
|
11
|
-
}).compileComponents();
|
|
12
|
-
|
|
13
|
-
fixture = TestBed.createComponent(MfAvatarComponent);
|
|
14
|
-
component = fixture.componentInstance;
|
|
15
|
-
fixture.detectChanges();
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
it('should create', () => {
|
|
19
|
-
expect(component).toBeTruthy();
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it('should show initials when no src', () => {
|
|
23
|
-
fixture.componentRef.setInput('name', 'John Doe');
|
|
24
|
-
fixture.detectChanges();
|
|
25
|
-
const span = fixture.nativeElement.querySelector('.mf-avatar');
|
|
26
|
-
expect(span?.textContent?.trim()).toBe('JD');
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it('should show ? when no name and no src', () => {
|
|
30
|
-
fixture.detectChanges();
|
|
31
|
-
const span = fixture.nativeElement.querySelector('.mf-avatar');
|
|
32
|
-
expect(span?.textContent?.trim()).toBe('?');
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it('should render img when src provided', () => {
|
|
36
|
-
fixture.componentRef.setInput('src', 'https://example.com/photo.jpg');
|
|
37
|
-
fixture.detectChanges();
|
|
38
|
-
const img = fixture.nativeElement.querySelector('img');
|
|
39
|
-
expect(img).toBeTruthy();
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it('should apply circle variant by default', () => {
|
|
43
|
-
expect(component.hostClasses()).toContain('mf-avatar--circle');
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it('should apply md size by default', () => {
|
|
47
|
-
expect(component.hostClasses()).toContain('mf-avatar--md');
|
|
48
|
-
});
|
|
49
|
-
});
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
ChangeDetectionStrategy,
|
|
3
|
-
Component,
|
|
4
|
-
computed,
|
|
5
|
-
effect,
|
|
6
|
-
input,
|
|
7
|
-
} from '@angular/core';
|
|
8
|
-
import { hasAccessibleName, warnInDev } from '../../a11y';
|
|
9
|
-
|
|
10
|
-
export type MfAvatarSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
|
11
|
-
export type MfAvatarVariant = 'circle' | 'rounded';
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Avatar de la librerÃa ng-comps.
|
|
15
|
-
* Muestra una imagen de perfil, iniciales o un icono.
|
|
16
|
-
* Componente puro sin dependencia de Angular Material.
|
|
17
|
-
*/
|
|
18
|
-
@Component({
|
|
19
|
-
selector: 'mf-avatar',
|
|
20
|
-
imports: [],
|
|
21
|
-
template: `
|
|
22
|
-
@if (src()) {
|
|
23
|
-
<img
|
|
24
|
-
[src]="src()"
|
|
25
|
-
[alt]="imageAlt()"
|
|
26
|
-
[class]="hostClasses()"
|
|
27
|
-
[attr.aria-hidden]="decorative() ? 'true' : null"
|
|
28
|
-
/>
|
|
29
|
-
} @else {
|
|
30
|
-
<span
|
|
31
|
-
[class]="hostClasses()"
|
|
32
|
-
[attr.aria-label]="fallbackAriaLabel()"
|
|
33
|
-
[attr.aria-hidden]="decorative() ? 'true' : null"
|
|
34
|
-
>
|
|
35
|
-
{{ initials() }}
|
|
36
|
-
</span>
|
|
37
|
-
}
|
|
38
|
-
`,
|
|
39
|
-
styleUrl: './mf-avatar.component.css',
|
|
40
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
41
|
-
})
|
|
42
|
-
export class MfAvatarComponent {
|
|
43
|
-
/** URL de la imagen */
|
|
44
|
-
readonly src = input<string | undefined>(undefined);
|
|
45
|
-
/** Texto alternativo */
|
|
46
|
-
readonly alt = input<string>('');
|
|
47
|
-
/** Nombre completo para generar iniciales */
|
|
48
|
-
readonly name = input<string>('');
|
|
49
|
-
/** Si es decorativo, se oculta a tecnologÃas asistivas */
|
|
50
|
-
readonly decorative = input(false);
|
|
51
|
-
/** Tamaño */
|
|
52
|
-
readonly size = input<MfAvatarSize>('md');
|
|
53
|
-
/** Forma */
|
|
54
|
-
readonly variant = input<MfAvatarVariant>('circle');
|
|
55
|
-
|
|
56
|
-
constructor() {
|
|
57
|
-
effect(() => {
|
|
58
|
-
if (!this.decorative() && !hasAccessibleName(this.alt(), this.name())) {
|
|
59
|
-
warnInDev(
|
|
60
|
-
'mf-avatar requiere `alt` o `name` cuando no es decorativo para exponer un nombre accesible.',
|
|
61
|
-
);
|
|
62
|
-
}
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
readonly initials = computed(() => {
|
|
67
|
-
const n = this.name();
|
|
68
|
-
if (!n) return '?';
|
|
69
|
-
const parts = n.trim().split(/\s+/);
|
|
70
|
-
if (parts.length === 1) return parts[0].charAt(0).toUpperCase();
|
|
71
|
-
return (
|
|
72
|
-
parts[0].charAt(0) + parts[parts.length - 1].charAt(0)
|
|
73
|
-
).toUpperCase();
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
readonly imageAlt = computed(() => {
|
|
77
|
-
if (this.decorative()) {
|
|
78
|
-
return '';
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return this.alt() || this.name();
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
readonly fallbackAriaLabel = computed(() => {
|
|
85
|
-
if (this.decorative()) {
|
|
86
|
-
return null;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return this.alt() || this.name() || null;
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
readonly hostClasses = computed(() => {
|
|
93
|
-
return [
|
|
94
|
-
'mf-avatar',
|
|
95
|
-
`mf-avatar--${this.size()}`,
|
|
96
|
-
`mf-avatar--${this.variant()}`,
|
|
97
|
-
].join(' ');
|
|
98
|
-
});
|
|
99
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
:host {
|
|
2
|
-
display: inline-block;
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
/* ── Brand ────────────────────────────────────────────────────── */
|
|
6
|
-
.mf-badge--brand .mat-badge-content {
|
|
7
|
-
background-color: var(--mf-color-brand) !important;
|
|
8
|
-
color: var(--mf-color-on-brand) !important;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
/* ── Accent ───────────────────────────────────────────────────── */
|
|
12
|
-
.mf-badge--accent .mat-badge-content {
|
|
13
|
-
background-color: var(--mf-color-accent-500) !important;
|
|
14
|
-
color: var(--mf-color-neutral-900) !important;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/* ── Error ────────────────────────────────────────────────────── */
|
|
18
|
-
.mf-badge--error .mat-badge-content {
|
|
19
|
-
background-color: var(--mf-color-error-500) !important;
|
|
20
|
-
color: var(--mf-color-neutral-0) !important;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/* ── Neutral ──────────────────────────────────────────────────── */
|
|
24
|
-
.mf-badge--neutral .mat-badge-content {
|
|
25
|
-
background-color: var(--mf-color-neutral-400) !important;
|
|
26
|
-
color: var(--mf-color-neutral-0) !important;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
.mat-badge-content {
|
|
30
|
-
font-family: var(--mf-font-base) !important;
|
|
31
|
-
font-weight: var(--mf-weight-bold) !important;
|
|
32
|
-
}
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
2
|
-
import { MfBadgeComponent } from './mf-badge.component';
|
|
3
|
-
|
|
4
|
-
describe('MfBadgeComponent', () => {
|
|
5
|
-
let fixture: ComponentFixture<MfBadgeComponent>;
|
|
6
|
-
let component: MfBadgeComponent;
|
|
7
|
-
|
|
8
|
-
beforeEach(async () => {
|
|
9
|
-
await TestBed.configureTestingModule({
|
|
10
|
-
imports: [MfBadgeComponent],
|
|
11
|
-
}).compileComponents();
|
|
12
|
-
|
|
13
|
-
fixture = TestBed.createComponent(MfBadgeComponent);
|
|
14
|
-
component = fixture.componentInstance;
|
|
15
|
-
fixture.detectChanges();
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
it('should create', () => {
|
|
19
|
-
expect(component).toBeTruthy();
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it('should apply brand class by default', () => {
|
|
23
|
-
expect(component.hostClasses()).toContain('mf-badge--brand');
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it('should apply error class when color is error', () => {
|
|
27
|
-
fixture.componentRef.setInput('color', 'error');
|
|
28
|
-
expect(component.hostClasses()).toContain('mf-badge--error');
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it('should map size correctly', () => {
|
|
32
|
-
fixture.componentRef.setInput('size', 'sm');
|
|
33
|
-
expect(component.matSize()).toBe('small');
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it('should map position correctly', () => {
|
|
37
|
-
fixture.componentRef.setInput('position', 'below-before');
|
|
38
|
-
expect(component.matPosition()).toBe('below before');
|
|
39
|
-
});
|
|
40
|
-
});
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
ChangeDetectionStrategy,
|
|
3
|
-
Component,
|
|
4
|
-
computed,
|
|
5
|
-
effect,
|
|
6
|
-
input,
|
|
7
|
-
} from '@angular/core';
|
|
8
|
-
import { MatBadgeModule } from '@angular/material/badge';
|
|
9
|
-
import { warnInDev } from '../../a11y';
|
|
10
|
-
|
|
11
|
-
export type MfBadgeColor = 'brand' | 'accent' | 'error' | 'neutral';
|
|
12
|
-
export type MfBadgeSize = 'sm' | 'md' | 'lg';
|
|
13
|
-
export type MfBadgePosition =
|
|
14
|
-
| 'above-after'
|
|
15
|
-
| 'above-before'
|
|
16
|
-
| 'below-after'
|
|
17
|
-
| 'below-before';
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Badge de la librerÃa ng-comps.
|
|
21
|
-
* Envuelve Angular Material `matBadge` y expone una API uniforme
|
|
22
|
-
* con look and feel de marca.
|
|
23
|
-
*/
|
|
24
|
-
@Component({
|
|
25
|
-
selector: 'mf-badge',
|
|
26
|
-
imports: [MatBadgeModule],
|
|
27
|
-
template: `
|
|
28
|
-
<span
|
|
29
|
-
[matBadge]="content()"
|
|
30
|
-
[matBadgeHidden]="hidden()"
|
|
31
|
-
[matBadgeDisabled]="disabled()"
|
|
32
|
-
[matBadgeOverlap]="overlap()"
|
|
33
|
-
[matBadgePosition]="matPosition()"
|
|
34
|
-
[matBadgeSize]="matSize()"
|
|
35
|
-
[matBadgeDescription]="badgeDescription()"
|
|
36
|
-
[attr.aria-hidden]="decorative() ? 'true' : null"
|
|
37
|
-
[class]="hostClasses()"
|
|
38
|
-
>
|
|
39
|
-
<ng-content />
|
|
40
|
-
</span>
|
|
41
|
-
`,
|
|
42
|
-
styleUrl: './mf-badge.component.css',
|
|
43
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
44
|
-
})
|
|
45
|
-
export class MfBadgeComponent {
|
|
46
|
-
/** Contenido del badge (texto o número) */
|
|
47
|
-
readonly content = input<string | number>('');
|
|
48
|
-
/** Descripción accesible del badge */
|
|
49
|
-
readonly description = input<string | undefined>(undefined);
|
|
50
|
-
/** Si es decorativo, se oculta a tecnologÃas asistivas */
|
|
51
|
-
readonly decorative = input(false);
|
|
52
|
-
/** Color semántico */
|
|
53
|
-
readonly color = input<MfBadgeColor>('brand');
|
|
54
|
-
/** Tamaño del badge */
|
|
55
|
-
readonly size = input<MfBadgeSize>('md');
|
|
56
|
-
/** Posición del badge */
|
|
57
|
-
readonly position = input<MfBadgePosition>('above-after');
|
|
58
|
-
/** Ocultar el badge */
|
|
59
|
-
readonly hidden = input(false);
|
|
60
|
-
/** Deshabilitado */
|
|
61
|
-
readonly disabled = input(false);
|
|
62
|
-
/** Superponer sobre el contenido */
|
|
63
|
-
readonly overlap = input(true);
|
|
64
|
-
|
|
65
|
-
constructor() {
|
|
66
|
-
effect(() => {
|
|
67
|
-
if (!this.decorative() && this.content() !== '' && !this.description()) {
|
|
68
|
-
warnInDev(
|
|
69
|
-
'mf-badge debería incluir `description` cuando el badge transmite información relevante.',
|
|
70
|
-
);
|
|
71
|
-
}
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
readonly badgeDescription = computed(() =>
|
|
76
|
-
this.decorative() ? '' : this.description() ?? '',
|
|
77
|
-
);
|
|
78
|
-
|
|
79
|
-
readonly matPosition = computed(() => {
|
|
80
|
-
const pos = this.position();
|
|
81
|
-
const map: Record<
|
|
82
|
-
MfBadgePosition,
|
|
83
|
-
'above after' | 'above before' | 'below after' | 'below before'
|
|
84
|
-
> = {
|
|
85
|
-
'above-after': 'above after',
|
|
86
|
-
'above-before': 'above before',
|
|
87
|
-
'below-after': 'below after',
|
|
88
|
-
'below-before': 'below before',
|
|
89
|
-
};
|
|
90
|
-
return map[pos];
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
readonly matSize = computed((): 'small' | 'medium' | 'large' => {
|
|
94
|
-
const map: Record<MfBadgeSize, 'small' | 'medium' | 'large'> = {
|
|
95
|
-
sm: 'small',
|
|
96
|
-
md: 'medium',
|
|
97
|
-
lg: 'large',
|
|
98
|
-
};
|
|
99
|
-
return map[this.size()];
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
readonly hostClasses = computed(() => {
|
|
103
|
-
return ['mf-badge', `mf-badge--${this.color()}`].join(' ');
|
|
104
|
-
});
|
|
105
|
-
}
|