@up_si_vale/sivale-componentes-angular 1.0.0 → 1.0.2
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/esm2022/lib/atoms/button/button.component.mjs +55 -0
- package/esm2022/lib/atoms/checkbox/checkbox.component.mjs +174 -0
- package/esm2022/lib/atoms/date-picker/date-picker.component.mjs +835 -0
- package/esm2022/lib/atoms/input/input.component.mjs +218 -0
- package/esm2022/lib/atoms/loader/loader.component.mjs +81 -0
- package/esm2022/lib/atoms/radio/radio.component.mjs +142 -0
- package/esm2022/lib/atoms/textarea/textarea.component.mjs +126 -0
- package/esm2022/lib/directives/asset-source.directive.mjs +68 -0
- package/esm2022/lib/directives/icon-source.directive.mjs +65 -0
- package/esm2022/lib/directives/no-leading-space.directive.mjs +24 -0
- package/esm2022/lib/directives/only-letters.directive.mjs +25 -0
- package/esm2022/lib/directives/only-number.directive.mjs +21 -0
- package/esm2022/lib/directives/rfc.directive.mjs +67 -0
- package/esm2022/lib/directives/tooltip.directive.mjs +199 -0
- package/esm2022/lib/models/snackbar.models.mjs +2 -0
- package/esm2022/lib/molecules/alert/alert.component.mjs +79 -0
- package/esm2022/lib/molecules/copy-input/copy-input.component.mjs +46 -0
- package/esm2022/lib/molecules/multiselect/multiselect.component.mjs +466 -0
- package/esm2022/lib/molecules/option/option.component.mjs +55 -0
- package/esm2022/lib/molecules/phone-input/phone-input.component.mjs +64 -0
- package/esm2022/lib/molecules/select/select.component.mjs +498 -0
- package/esm2022/lib/molecules/snackbar/snackbar.component.mjs +581 -0
- package/esm2022/lib/organisms/sidebar/sidebar-icon.component.mjs +91 -0
- package/esm2022/lib/organisms/sidebar/sidebar.component.mjs +115 -0
- package/esm2022/lib/services/snackbar.service.mjs +96 -0
- package/esm2022/lib/tokens/asset-base-url.token.mjs +10 -0
- package/esm2022/public-api.mjs +37 -0
- package/esm2022/up_si_vale-sivale-componentes-angular.mjs +5 -0
- package/fesm2022/up_si_vale-sivale-componentes-angular.mjs +4116 -0
- package/fesm2022/up_si_vale-sivale-componentes-angular.mjs.map +1 -0
- package/index.d.ts +5 -0
- package/lib/atoms/button/button.component.d.ts +20 -0
- package/lib/atoms/checkbox/checkbox.component.d.ts +31 -0
- package/lib/atoms/date-picker/date-picker.component.d.ts +134 -0
- package/lib/atoms/input/input.component.d.ts +35 -0
- package/lib/atoms/loader/loader.component.d.ts +10 -0
- package/lib/atoms/radio/radio.component.d.ts +25 -0
- package/lib/atoms/textarea/textarea.component.d.ts +27 -0
- package/lib/directives/asset-source.directive.d.ts +19 -0
- package/lib/directives/icon-source.directive.d.ts +18 -0
- package/lib/directives/no-leading-space.directive.d.ts +6 -0
- package/lib/directives/only-letters.directive.d.ts +7 -0
- package/lib/directives/only-number.directive.d.ts +6 -0
- package/lib/directives/rfc.directive.d.ts +12 -0
- package/lib/directives/tooltip.directive.d.ts +27 -0
- package/{src/lib/models/snackbar.models.ts → lib/models/snackbar.models.d.ts} +8 -10
- package/lib/molecules/alert/alert.component.d.ts +29 -0
- package/lib/molecules/copy-input/copy-input.component.d.ts +14 -0
- package/lib/molecules/multiselect/multiselect.component.d.ts +74 -0
- package/lib/molecules/option/option.component.d.ts +19 -0
- package/lib/molecules/phone-input/phone-input.component.d.ts +12 -0
- package/lib/molecules/select/select.component.d.ts +88 -0
- package/lib/molecules/snackbar/snackbar.component.d.ts +17 -0
- package/lib/organisms/sidebar/sidebar-icon.component.d.ts +8 -0
- package/lib/organisms/sidebar/sidebar.component.d.ts +47 -0
- package/lib/services/snackbar.service.d.ts +40 -0
- package/{src/lib/tokens/asset-base-url.token.ts → lib/tokens/asset-base-url.token.d.ts} +1 -5
- package/package.json +39 -28
- package/{src/public-api.ts → public-api.d.ts} +3 -15
- package/ng-package.json +0 -9
- package/src/lib/atoms/button/button.component.html +0 -21
- package/src/lib/atoms/button/button.component.scss +0 -242
- package/src/lib/atoms/button/button.component.ts +0 -43
- package/src/lib/atoms/checkbox/checkbox.component.scss +0 -131
- package/src/lib/atoms/checkbox/checkbox.component.ts +0 -130
- package/src/lib/atoms/date-picker/date-picker.component.html +0 -295
- package/src/lib/atoms/date-picker/date-picker.component.scss +0 -1002
- package/src/lib/atoms/date-picker/date-picker.component.ts +0 -942
- package/src/lib/atoms/input/input.component.ts +0 -239
- package/src/lib/atoms/loader/loader.component.scss +0 -144
- package/src/lib/atoms/loader/loader.component.ts +0 -44
- package/src/lib/atoms/radio/radio.component.scss +0 -115
- package/src/lib/atoms/radio/radio.component.ts +0 -103
- package/src/lib/atoms/textarea/textarea.component.ts +0 -155
- package/src/lib/directives/asset-source.directive.ts +0 -64
- package/src/lib/directives/icon-source.directive.ts +0 -74
- package/src/lib/directives/no-leading-space.directive.ts +0 -18
- package/src/lib/directives/only-letters.directive.ts +0 -21
- package/src/lib/directives/only-number.directive.ts +0 -15
- package/src/lib/directives/rfc.directive.ts +0 -60
- package/src/lib/directives/tooltip.directive.ts +0 -211
- package/src/lib/molecules/copy-input/copy-input.component.html +0 -29
- package/src/lib/molecules/copy-input/copy-input.component.scss +0 -101
- package/src/lib/molecules/copy-input/copy-input.component.ts +0 -41
- package/src/lib/molecules/multiselect/multiselect.component.scss +0 -298
- package/src/lib/molecules/multiselect/multiselect.component.ts +0 -487
- package/src/lib/molecules/option/option.component.ts +0 -45
- package/src/lib/molecules/phone-input/phone-input.component.scss +0 -78
- package/src/lib/molecules/phone-input/phone-input.component.ts +0 -43
- package/src/lib/molecules/select/select.component.ts +0 -482
- package/src/lib/molecules/snackbar/snackbar.component.scss +0 -201
- package/src/lib/molecules/snackbar/snackbar.component.ts +0 -321
- package/src/lib/services/snackbar.service.ts +0 -103
- package/tsconfig.json +0 -31
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
import { Component, Input, forwardRef, ChangeDetectorRef, ChangeDetectionStrategy } from '@angular/core';
|
|
2
|
-
import { CommonModule } from '@angular/common';
|
|
3
|
-
import { ControlValueAccessor, NG_VALUE_ACCESSOR, ReactiveFormsModule, FormsModule } from '@angular/forms';
|
|
4
|
-
|
|
5
|
-
@Component({
|
|
6
|
-
selector: 'app-textarea',
|
|
7
|
-
standalone: true,
|
|
8
|
-
imports: [CommonModule, ReactiveFormsModule, FormsModule],
|
|
9
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
10
|
-
template: `
|
|
11
|
-
<div class="textarea-wrapper">
|
|
12
|
-
<label *ngIf="label" [for]="id" class="textarea-label">{{ label }}</label>
|
|
13
|
-
<div class="textarea-container" [class.error]="hasError">
|
|
14
|
-
<textarea
|
|
15
|
-
[id]="id"
|
|
16
|
-
[placeholder]="placeholder"
|
|
17
|
-
[rows]="rows"
|
|
18
|
-
[disabled]="disabled"
|
|
19
|
-
[readonly]="readonly"
|
|
20
|
-
[attr.maxlength]="maxLength"
|
|
21
|
-
class="textarea-field"
|
|
22
|
-
[class.error]="hasError"
|
|
23
|
-
[value]="value"
|
|
24
|
-
(blur)="onBlur()"
|
|
25
|
-
(input)="onInputChange($event)"
|
|
26
|
-
></textarea>
|
|
27
|
-
</div>
|
|
28
|
-
<div *ngIf="hasError && errorMessage" class="field-error">
|
|
29
|
-
{{ errorMessage }}
|
|
30
|
-
</div>
|
|
31
|
-
</div>
|
|
32
|
-
`,
|
|
33
|
-
styles: [`
|
|
34
|
-
.textarea-wrapper {
|
|
35
|
-
width: 100%;
|
|
36
|
-
display: flex;
|
|
37
|
-
flex-direction: column;
|
|
38
|
-
gap: 6px;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
.textarea-label {
|
|
42
|
-
font-family: 'Quicksand', sans-serif;
|
|
43
|
-
font-weight: 500;
|
|
44
|
-
font-size: 14px;
|
|
45
|
-
line-height: 20px;
|
|
46
|
-
color: #414651;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
.textarea-container {
|
|
50
|
-
width: 100%;
|
|
51
|
-
|
|
52
|
-
&.error .textarea-field {
|
|
53
|
-
border-color: #F04438;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
.textarea-field {
|
|
58
|
-
width: 100%;
|
|
59
|
-
padding: 10px 14px;
|
|
60
|
-
background: #FFFFFF;
|
|
61
|
-
border: 1px solid #D5D7DA;
|
|
62
|
-
border-radius: 8px;
|
|
63
|
-
font-family: 'Quicksand', sans-serif;
|
|
64
|
-
font-weight: 400;
|
|
65
|
-
font-size: 16px;
|
|
66
|
-
line-height: 24px;
|
|
67
|
-
color: #2E353A;
|
|
68
|
-
resize: vertical;
|
|
69
|
-
min-height: 100px;
|
|
70
|
-
transition: all 0.2s ease;
|
|
71
|
-
box-sizing: border-box;
|
|
72
|
-
|
|
73
|
-
&::placeholder {
|
|
74
|
-
color: #A4A7AE;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
&:focus {
|
|
78
|
-
outline: none;
|
|
79
|
-
border-color: #F59100;
|
|
80
|
-
box-shadow: 0 0 0 4px rgba(245, 145, 0, 0.1);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
&:disabled {
|
|
84
|
-
background: #F9FAFB;
|
|
85
|
-
cursor: not-allowed;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
&.error {
|
|
89
|
-
border-color: #F04438;
|
|
90
|
-
|
|
91
|
-
&:focus {
|
|
92
|
-
box-shadow: 0 0 0 4px rgba(240, 68, 56, 0.1);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
.field-error {
|
|
98
|
-
color: #F04438;
|
|
99
|
-
font-size: 0.875rem;
|
|
100
|
-
font-family: 'Quicksand', sans-serif;
|
|
101
|
-
}
|
|
102
|
-
`],
|
|
103
|
-
providers: [
|
|
104
|
-
{
|
|
105
|
-
provide: NG_VALUE_ACCESSOR,
|
|
106
|
-
useExisting: forwardRef(() => TextareaComponent),
|
|
107
|
-
multi: true
|
|
108
|
-
}
|
|
109
|
-
]
|
|
110
|
-
})
|
|
111
|
-
export class TextareaComponent implements ControlValueAccessor {
|
|
112
|
-
@Input() label = '';
|
|
113
|
-
@Input() id = '';
|
|
114
|
-
@Input() placeholder = '';
|
|
115
|
-
@Input() rows = 4;
|
|
116
|
-
@Input() maxLength?: number;
|
|
117
|
-
@Input() hasError = false;
|
|
118
|
-
@Input() errorMessage = '';
|
|
119
|
-
@Input() disabled = false;
|
|
120
|
-
@Input() readonly = false;
|
|
121
|
-
|
|
122
|
-
value = '';
|
|
123
|
-
onChange: any = () => {};
|
|
124
|
-
onTouched: any = () => {};
|
|
125
|
-
|
|
126
|
-
constructor(private cdr: ChangeDetectorRef) {}
|
|
127
|
-
|
|
128
|
-
writeValue(value: any): void {
|
|
129
|
-
this.value = value || '';
|
|
130
|
-
this.cdr.markForCheck();
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
registerOnChange(fn: any): void {
|
|
134
|
-
this.onChange = fn;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
registerOnTouched(fn: any): void {
|
|
138
|
-
this.onTouched = fn;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
setDisabledState(isDisabled: boolean): void {
|
|
142
|
-
this.disabled = isDisabled;
|
|
143
|
-
this.cdr.markForCheck();
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
onInputChange(event: Event): void {
|
|
147
|
-
const target = event.target as HTMLTextAreaElement;
|
|
148
|
-
this.value = target.value;
|
|
149
|
-
this.onChange(this.value);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
onBlur(): void {
|
|
153
|
-
this.onTouched();
|
|
154
|
-
}
|
|
155
|
-
}
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import { Directive, Input, ElementRef, OnInit, OnChanges, AfterViewInit, Inject, Optional } from '@angular/core';
|
|
2
|
-
import { ASSET_BASE_URL } from '../tokens/asset-base-url.token';
|
|
3
|
-
|
|
4
|
-
export type AssetSource = 'mf' | 'main';
|
|
5
|
-
|
|
6
|
-
@Directive({
|
|
7
|
-
selector: '[assetSource]',
|
|
8
|
-
standalone: true
|
|
9
|
-
})
|
|
10
|
-
export class AssetSourceDirective implements OnInit, OnChanges, AfterViewInit {
|
|
11
|
-
@Input() assetSource: AssetSource = 'mf';
|
|
12
|
-
@Input() assetPath?: string;
|
|
13
|
-
|
|
14
|
-
private originalSrc: string | null = null;
|
|
15
|
-
|
|
16
|
-
constructor(
|
|
17
|
-
private el: ElementRef<HTMLImageElement>,
|
|
18
|
-
@Optional() @Inject(ASSET_BASE_URL) private assetBaseUrl: string | null
|
|
19
|
-
) {}
|
|
20
|
-
|
|
21
|
-
ngOnInit() {
|
|
22
|
-
this.captureOriginalSrc();
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
ngAfterViewInit() {
|
|
26
|
-
this.updateSrc();
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
ngOnChanges() {
|
|
30
|
-
this.updateSrc();
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
private captureOriginalSrc() {
|
|
34
|
-
if (this.el.nativeElement.tagName === 'IMG') {
|
|
35
|
-
this.originalSrc = this.el.nativeElement.getAttribute('src');
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
private updateSrc() {
|
|
40
|
-
const assetPath = this.assetPath || this.originalSrc;
|
|
41
|
-
|
|
42
|
-
if (!assetPath) {
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const baseUrl = this.getBaseUrl();
|
|
47
|
-
const fullPath = `${baseUrl}/${assetPath}`;
|
|
48
|
-
|
|
49
|
-
if (this.el.nativeElement.tagName === 'IMG') {
|
|
50
|
-
this.el.nativeElement.src = fullPath;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
private getBaseUrl(): string {
|
|
55
|
-
switch (this.assetSource) {
|
|
56
|
-
case 'mf':
|
|
57
|
-
return this.assetBaseUrl ?? '';
|
|
58
|
-
case 'main':
|
|
59
|
-
return '/assets';
|
|
60
|
-
default:
|
|
61
|
-
return this.assetBaseUrl ?? '';
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
}
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import { Directive, Input, ElementRef, OnInit, OnChanges, AfterViewInit, Inject, Optional } from '@angular/core';
|
|
2
|
-
import { ASSET_BASE_URL } from '../tokens/asset-base-url.token';
|
|
3
|
-
|
|
4
|
-
export type IconName =
|
|
5
|
-
'user'
|
|
6
|
-
| 'mail'
|
|
7
|
-
| 'build'
|
|
8
|
-
| 'map-pin'
|
|
9
|
-
| 'money'
|
|
10
|
-
| 'card'
|
|
11
|
-
| 'nav-arrow-up'
|
|
12
|
-
| 'nav-arrow-down'
|
|
13
|
-
| 'car'
|
|
14
|
-
| 'trash'
|
|
15
|
-
| 'hand-card'
|
|
16
|
-
| 'help-outlined'
|
|
17
|
-
| 'edit'
|
|
18
|
-
| 'shopping-cart';
|
|
19
|
-
|
|
20
|
-
@Directive({
|
|
21
|
-
selector: 'i[iconName]',
|
|
22
|
-
standalone: true
|
|
23
|
-
})
|
|
24
|
-
export class IconSourceDirective implements OnInit, OnChanges, AfterViewInit {
|
|
25
|
-
@Input() iconName!: IconName;
|
|
26
|
-
@Input() assetSource: 'mf' | 'main' = 'mf';
|
|
27
|
-
|
|
28
|
-
constructor(
|
|
29
|
-
private el: ElementRef<HTMLElement>,
|
|
30
|
-
@Optional() @Inject(ASSET_BASE_URL) private assetBaseUrl: string | null
|
|
31
|
-
) {
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
ngOnInit() {
|
|
35
|
-
this.addIconClass();
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
ngAfterViewInit() {
|
|
39
|
-
this.setIconUrl();
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
ngOnChanges() {
|
|
43
|
-
this.setIconUrl();
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
private addIconClass() {
|
|
47
|
-
if (this.iconName) {
|
|
48
|
-
this.el.nativeElement.classList.add(`icon-${this.iconName}`);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
private setIconUrl() {
|
|
53
|
-
if (!this.iconName) {
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const baseUrl = this.getBaseUrl();
|
|
58
|
-
const iconUrl = `${baseUrl}/icons/${this.iconName}.svg`;
|
|
59
|
-
|
|
60
|
-
this.el.nativeElement.style.maskImage = `url('${iconUrl}')`;
|
|
61
|
-
this.el.nativeElement.style.webkitMaskImage = `url('${iconUrl}')`;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
private getBaseUrl(): string {
|
|
65
|
-
switch (this.assetSource) {
|
|
66
|
-
case 'mf':
|
|
67
|
-
return this.assetBaseUrl ?? '';
|
|
68
|
-
case 'main':
|
|
69
|
-
return '/assets';
|
|
70
|
-
default:
|
|
71
|
-
return this.assetBaseUrl ?? '';
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { Directive, HostListener } from '@angular/core';
|
|
2
|
-
|
|
3
|
-
@Directive({
|
|
4
|
-
selector: '[noLeadingSpace]',
|
|
5
|
-
standalone: true
|
|
6
|
-
})
|
|
7
|
-
export class NoLeadingSpaceDirective {
|
|
8
|
-
|
|
9
|
-
@HostListener('input', ['$event'])
|
|
10
|
-
onInput(event: any) {
|
|
11
|
-
const value = event.target.value;
|
|
12
|
-
|
|
13
|
-
// Elimina espacios al inicio
|
|
14
|
-
if (value.startsWith(' ')) {
|
|
15
|
-
event.target.value = value.trimStart();
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { Directive, HostListener } from '@angular/core';
|
|
2
|
-
|
|
3
|
-
@Directive({
|
|
4
|
-
selector: '[onlyLetters]',
|
|
5
|
-
standalone: true
|
|
6
|
-
})
|
|
7
|
-
|
|
8
|
-
export class OnlyLettersDirective {
|
|
9
|
-
|
|
10
|
-
private regex = /^[A-Za-zÁÉÍÓÚÜÑáéíóúüñ' -]*$/;
|
|
11
|
-
|
|
12
|
-
@HostListener('input', ['$event'])
|
|
13
|
-
onInput(event: any) {
|
|
14
|
-
const value: string = event.target.value;
|
|
15
|
-
|
|
16
|
-
event.target.value = value
|
|
17
|
-
.split('')
|
|
18
|
-
.filter(char => this.regex.test(char))
|
|
19
|
-
.join('');
|
|
20
|
-
}
|
|
21
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { Directive, HostListener } from '@angular/core';
|
|
2
|
-
|
|
3
|
-
@Directive({
|
|
4
|
-
selector: '[onlyNumber]',
|
|
5
|
-
standalone: true
|
|
6
|
-
})
|
|
7
|
-
|
|
8
|
-
export class OnlyNumberDirective {
|
|
9
|
-
@HostListener('input', ['$event'])
|
|
10
|
-
onInput(event: any) {
|
|
11
|
-
const value = event.target.value;
|
|
12
|
-
event.target.value = value.replace(/\D/g, '');
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
}
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { Directive, HostListener, Input, Optional, Self } from '@angular/core';
|
|
2
|
-
import { NgControl } from '@angular/forms';
|
|
3
|
-
|
|
4
|
-
@Directive({
|
|
5
|
-
selector: '[rfcValidator]',
|
|
6
|
-
standalone: true
|
|
7
|
-
})
|
|
8
|
-
export class RfcDirective {
|
|
9
|
-
|
|
10
|
-
@Input() tipoContribuyente: string | null = null;
|
|
11
|
-
|
|
12
|
-
constructor(@Optional() @Self() private ngControl: NgControl | null) {
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
// Función para obtener el límite según el tipo de contribuyente
|
|
16
|
-
private getMaxLength(): number {
|
|
17
|
-
if (this.tipoContribuyente === 'fisica') return 13;
|
|
18
|
-
if (this.tipoContribuyente === 'moral') return 12;
|
|
19
|
-
return 13; // Default por si no llega el valor
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
@HostListener('input', ['$event'])
|
|
23
|
-
onInput(event: Event) {
|
|
24
|
-
const input = event.target as HTMLInputElement;
|
|
25
|
-
let value = input.value;
|
|
26
|
-
|
|
27
|
-
// Solo letras y números
|
|
28
|
-
value = value.replace(/[^a-zA-Z0-9]/g, '');
|
|
29
|
-
|
|
30
|
-
// Mayúsculas
|
|
31
|
-
value = value.toUpperCase();
|
|
32
|
-
|
|
33
|
-
// Aplicar límite dinámico
|
|
34
|
-
const max = this.getMaxLength();
|
|
35
|
-
if (value.length > max) {
|
|
36
|
-
value = value.substring(0, max);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
this.ngControl?.control?.setValue(value, { emitEvent: false });
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
@HostListener('paste', ['$event'])
|
|
43
|
-
onPaste(event: ClipboardEvent) {
|
|
44
|
-
const pasted = event.clipboardData?.getData('text') || '';
|
|
45
|
-
const max = this.getMaxLength();
|
|
46
|
-
|
|
47
|
-
// Bloquear pegado con caracteres inválidos
|
|
48
|
-
if (/[^a-zA-Z0-9]/.test(pasted)) {
|
|
49
|
-
event.preventDefault();
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Bloquear pegado si excede el máximo
|
|
54
|
-
if (pasted.length > max) {
|
|
55
|
-
event.preventDefault();
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
|
|
@@ -1,211 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Directive,
|
|
3
|
-
Input,
|
|
4
|
-
ElementRef,
|
|
5
|
-
HostListener,
|
|
6
|
-
Renderer2,
|
|
7
|
-
OnDestroy,
|
|
8
|
-
HostBinding
|
|
9
|
-
} from '@angular/core';
|
|
10
|
-
|
|
11
|
-
type TooltipPosition = 'top' | 'bottom' | 'left' | 'right' | 'auto';
|
|
12
|
-
|
|
13
|
-
@Directive({
|
|
14
|
-
selector: '[appTooltip]',
|
|
15
|
-
standalone: true
|
|
16
|
-
})
|
|
17
|
-
export class TooltipDirective implements OnDestroy {
|
|
18
|
-
@Input('appTooltip') tooltipText = '';
|
|
19
|
-
@Input() tooltipPosition: TooltipPosition = 'auto';
|
|
20
|
-
@Input() tooltipDelay = 300;
|
|
21
|
-
@Input() tooltipMaxWidth = 280;
|
|
22
|
-
@Input() tooltipClass = '';
|
|
23
|
-
|
|
24
|
-
@HostBinding('style.cursor') cursor = 'help';
|
|
25
|
-
|
|
26
|
-
private tooltipElement: HTMLElement | null = null;
|
|
27
|
-
private showTimeout?: number;
|
|
28
|
-
private hideTimeout?: number;
|
|
29
|
-
|
|
30
|
-
constructor(
|
|
31
|
-
private el: ElementRef,
|
|
32
|
-
private renderer: Renderer2
|
|
33
|
-
) {}
|
|
34
|
-
|
|
35
|
-
@HostListener('mouseenter')
|
|
36
|
-
onMouseEnter(): void {
|
|
37
|
-
if (this.hideTimeout) {
|
|
38
|
-
clearTimeout(this.hideTimeout);
|
|
39
|
-
this.hideTimeout = undefined;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
this.showTimeout = window.setTimeout(() => {
|
|
43
|
-
this.showTooltip();
|
|
44
|
-
}, this.tooltipDelay);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
@HostListener('mouseleave')
|
|
48
|
-
onMouseLeave(): void {
|
|
49
|
-
if (this.showTimeout) {
|
|
50
|
-
clearTimeout(this.showTimeout);
|
|
51
|
-
this.showTimeout = undefined;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
this.hideTimeout = window.setTimeout(() => {
|
|
55
|
-
this.hideTooltip();
|
|
56
|
-
}, 100);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
@HostListener('click')
|
|
60
|
-
onClick(): void {
|
|
61
|
-
// Allow click events to propagate
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
private showTooltip(): void {
|
|
65
|
-
if (!this.tooltipText || this.tooltipElement) {
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Create tooltip element
|
|
70
|
-
this.tooltipElement = this.renderer.createElement('div');
|
|
71
|
-
this.renderer.addClass(this.tooltipElement, 'app-tooltip');
|
|
72
|
-
|
|
73
|
-
if (this.tooltipClass) {
|
|
74
|
-
this.renderer.addClass(this.tooltipElement, this.tooltipClass);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Create tooltip content
|
|
78
|
-
const tooltipContent = this.renderer.createElement('div');
|
|
79
|
-
this.renderer.addClass(tooltipContent, 'app-tooltip-content');
|
|
80
|
-
this.renderer.setStyle(tooltipContent, 'max-width', `${this.tooltipMaxWidth}px`);
|
|
81
|
-
|
|
82
|
-
const textNode = this.renderer.createText(this.tooltipText);
|
|
83
|
-
this.renderer.appendChild(tooltipContent, textNode);
|
|
84
|
-
this.renderer.appendChild(this.tooltipElement, tooltipContent);
|
|
85
|
-
|
|
86
|
-
// Create arrow element
|
|
87
|
-
const arrow = this.renderer.createElement('div');
|
|
88
|
-
this.renderer.addClass(arrow, 'app-tooltip-arrow');
|
|
89
|
-
this.renderer.appendChild(this.tooltipElement, arrow);
|
|
90
|
-
|
|
91
|
-
// Add to body
|
|
92
|
-
this.renderer.appendChild(document.body, this.tooltipElement);
|
|
93
|
-
|
|
94
|
-
// Position tooltip
|
|
95
|
-
this.positionTooltip();
|
|
96
|
-
|
|
97
|
-
// Trigger animation
|
|
98
|
-
requestAnimationFrame(() => {
|
|
99
|
-
if (this.tooltipElement) {
|
|
100
|
-
this.renderer.addClass(this.tooltipElement, 'app-tooltip-show');
|
|
101
|
-
}
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
private hideTooltip(): void {
|
|
106
|
-
if (!this.tooltipElement) {
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
this.renderer.removeClass(this.tooltipElement, 'app-tooltip-show');
|
|
111
|
-
|
|
112
|
-
setTimeout(() => {
|
|
113
|
-
if (this.tooltipElement) {
|
|
114
|
-
this.renderer.removeChild(document.body, this.tooltipElement);
|
|
115
|
-
this.tooltipElement = null;
|
|
116
|
-
}
|
|
117
|
-
}, 200);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
private positionTooltip(): void {
|
|
121
|
-
if (!this.tooltipElement) {
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const hostRect = this.el.nativeElement.getBoundingClientRect();
|
|
126
|
-
const tooltipRect = this.tooltipElement.getBoundingClientRect();
|
|
127
|
-
const scrollX = window.scrollX || window.pageXOffset;
|
|
128
|
-
const scrollY = window.scrollY || window.pageYOffset;
|
|
129
|
-
const gap = 16; // Space between element and tooltip
|
|
130
|
-
|
|
131
|
-
// Calculate available space in each direction
|
|
132
|
-
const spaceTop = hostRect.top;
|
|
133
|
-
const spaceBottom = window.innerHeight - hostRect.bottom;
|
|
134
|
-
const spaceLeft = hostRect.left;
|
|
135
|
-
const spaceRight = window.innerWidth - hostRect.right;
|
|
136
|
-
|
|
137
|
-
let position = this.tooltipPosition;
|
|
138
|
-
let top = 0;
|
|
139
|
-
let left = 0;
|
|
140
|
-
|
|
141
|
-
// Auto-detect best position
|
|
142
|
-
if (position === 'auto') {
|
|
143
|
-
if (spaceTop >= tooltipRect.height + gap) {
|
|
144
|
-
position = 'top';
|
|
145
|
-
} else if (spaceBottom >= tooltipRect.height + gap) {
|
|
146
|
-
position = 'bottom';
|
|
147
|
-
} else if (spaceRight >= tooltipRect.width + gap) {
|
|
148
|
-
position = 'right';
|
|
149
|
-
} else if (spaceLeft >= tooltipRect.width + gap) {
|
|
150
|
-
position = 'left';
|
|
151
|
-
} else {
|
|
152
|
-
// Default to top if no space is sufficient
|
|
153
|
-
position = 'top';
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// Calculate position based on detected/specified position
|
|
158
|
-
switch (position) {
|
|
159
|
-
case 'top':
|
|
160
|
-
top = hostRect.top + scrollY - tooltipRect.height - gap;
|
|
161
|
-
left = hostRect.left + scrollX + (hostRect.width / 2) - (tooltipRect.width / 2);
|
|
162
|
-
break;
|
|
163
|
-
|
|
164
|
-
case 'bottom':
|
|
165
|
-
top = hostRect.bottom + scrollY + gap;
|
|
166
|
-
left = hostRect.left + scrollX + (hostRect.width / 2) - (tooltipRect.width / 2);
|
|
167
|
-
break;
|
|
168
|
-
|
|
169
|
-
case 'left':
|
|
170
|
-
top = hostRect.top + scrollY + (hostRect.height / 2) - (tooltipRect.height / 2);
|
|
171
|
-
left = hostRect.left + scrollX - tooltipRect.width - gap;
|
|
172
|
-
break;
|
|
173
|
-
|
|
174
|
-
case 'right':
|
|
175
|
-
top = hostRect.top + scrollY + (hostRect.height / 2) - (tooltipRect.height / 2);
|
|
176
|
-
left = hostRect.right + scrollX + gap;
|
|
177
|
-
break;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// Adjust horizontal position to keep tooltip within viewport
|
|
181
|
-
if (position === 'top' || position === 'bottom') {
|
|
182
|
-
const minLeft = scrollX + 10;
|
|
183
|
-
const maxLeft = scrollX + window.innerWidth - tooltipRect.width - 10;
|
|
184
|
-
left = Math.max(minLeft, Math.min(left, maxLeft));
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// Adjust vertical position to keep tooltip within viewport
|
|
188
|
-
if (position === 'left' || position === 'right') {
|
|
189
|
-
const minTop = scrollY + 10;
|
|
190
|
-
const maxTop = scrollY + window.innerHeight - tooltipRect.height - 10;
|
|
191
|
-
top = Math.max(minTop, Math.min(top, maxTop));
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// Apply position
|
|
195
|
-
this.renderer.setStyle(this.tooltipElement, 'top', `${top}px`);
|
|
196
|
-
this.renderer.setStyle(this.tooltipElement, 'left', `${left}px`);
|
|
197
|
-
|
|
198
|
-
// Add position class for arrow styling
|
|
199
|
-
this.renderer.addClass(this.tooltipElement, `app-tooltip-${position}`);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
ngOnDestroy(): void {
|
|
203
|
-
if (this.showTimeout) {
|
|
204
|
-
clearTimeout(this.showTimeout);
|
|
205
|
-
}
|
|
206
|
-
if (this.hideTimeout) {
|
|
207
|
-
clearTimeout(this.hideTimeout);
|
|
208
|
-
}
|
|
209
|
-
this.hideTooltip();
|
|
210
|
-
}
|
|
211
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
<div class="copy-input-wrapper">
|
|
2
|
-
|
|
3
|
-
<!-- Tooltip -->
|
|
4
|
-
<div
|
|
5
|
-
class="copy-tooltip"
|
|
6
|
-
*ngIf="isCopied"
|
|
7
|
-
>
|
|
8
|
-
Copiado
|
|
9
|
-
</div>
|
|
10
|
-
|
|
11
|
-
<!-- Componente -->
|
|
12
|
-
<div
|
|
13
|
-
class="copy-input"
|
|
14
|
-
(click)="copyToClipboard()"
|
|
15
|
-
tabindex="0"
|
|
16
|
-
role="button"
|
|
17
|
-
[attr.aria-label]="ariaLabel"
|
|
18
|
-
(keydown.enter)="copyToClipboard()"
|
|
19
|
-
>
|
|
20
|
-
<i
|
|
21
|
-
class="copy-input__icon"
|
|
22
|
-
[ngClass]="isCopied ? copiedIcon : icon">
|
|
23
|
-
</i>
|
|
24
|
-
|
|
25
|
-
<span class="copy-input__text">
|
|
26
|
-
{{ value }}
|
|
27
|
-
</span>
|
|
28
|
-
</div>
|
|
29
|
-
</div>
|