ngx-opalbytes-components 1.18.0 → 1.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/README.md +103 -0
  2. package/cli/cao-comp.cjs +186 -0
  3. package/fesm2022/ngx-opalbytes-components.mjs +331 -76
  4. package/fesm2022/ngx-opalbytes-components.mjs.map +1 -1
  5. package/package.json +4 -1
  6. package/src/lib/shared/components/autocomplete/autocomplete.component.html +126 -0
  7. package/src/lib/shared/components/autocomplete/autocomplete.component.scss +150 -0
  8. package/src/lib/shared/components/autocomplete/autocomplete.component.ts +552 -0
  9. package/src/lib/shared/components/base-button/base-button.component.html +33 -0
  10. package/src/lib/shared/components/base-button/base-button.component.scss +14 -0
  11. package/src/lib/shared/components/base-button/base-button.component.spec.ts +64 -0
  12. package/src/lib/shared/components/base-button/base-button.component.ts +48 -0
  13. package/src/lib/shared/components/base-icon/base-icon.css +0 -0
  14. package/src/lib/shared/components/base-icon/base-icon.html +11 -0
  15. package/src/lib/shared/components/base-icon/base-icon.spec.ts +23 -0
  16. package/src/lib/shared/components/base-icon/base-icon.ts +18 -0
  17. package/src/lib/shared/components/base-input/base-input.css +193 -0
  18. package/src/lib/shared/components/base-input/base-input.html +43 -0
  19. package/src/lib/shared/components/base-input/base-input.spec.ts +146 -0
  20. package/src/lib/shared/components/base-input/base-input.ts +133 -0
  21. package/src/lib/shared/components/base-time-range/base-time-range.css +27 -0
  22. package/src/lib/shared/components/base-time-range/base-time-range.html +23 -0
  23. package/src/lib/shared/components/base-time-range/base-time-range.spec.ts +27 -0
  24. package/src/lib/shared/components/base-time-range/base-time-range.ts +61 -0
  25. package/src/lib/shared/components/base-toggle/base-toggle.component.html +28 -0
  26. package/src/lib/shared/components/base-toggle/base-toggle.component.scss +45 -0
  27. package/src/lib/shared/components/base-toggle/base-toggle.component.spec.ts +22 -0
  28. package/src/lib/shared/components/base-toggle/base-toggle.component.ts +138 -0
  29. package/src/lib/shared/components/card/card.component.css +264 -0
  30. package/src/lib/shared/components/card/card.component.html +103 -0
  31. package/src/lib/shared/components/card/card.component.ts +63 -0
  32. package/src/lib/shared/components/date-picker/date-picker.html +82 -0
  33. package/src/lib/shared/components/date-picker/date-picker.scss +8 -0
  34. package/src/lib/shared/components/date-picker/date-picker.spec.ts +23 -0
  35. package/src/lib/shared/components/date-picker/date-picker.ts +165 -0
  36. package/src/lib/shared/components/drop-down/drop-down.css +61 -0
  37. package/src/lib/shared/components/drop-down/drop-down.html +96 -0
  38. package/src/lib/shared/components/drop-down/drop-down.spec.ts +335 -0
  39. package/src/lib/shared/components/drop-down/drop-down.ts +335 -0
  40. package/src/lib/shared/components/footer/footer.css +29 -0
  41. package/src/lib/shared/components/footer/footer.html +10 -0
  42. package/src/lib/shared/components/footer/footer.spec.ts +22 -0
  43. package/src/lib/shared/components/footer/footer.ts +15 -0
  44. package/src/lib/shared/components/index.ts +19 -0
  45. package/src/lib/shared/components/kpi/kpi.css +0 -0
  46. package/src/lib/shared/components/kpi/kpi.html +11 -0
  47. package/src/lib/shared/components/kpi/kpi.spec.ts +38 -0
  48. package/src/lib/shared/components/kpi/kpi.ts +25 -0
  49. package/src/lib/shared/components/links-button/links-button.css +70 -0
  50. package/src/lib/shared/components/links-button/links-button.html +23 -0
  51. package/src/lib/shared/components/links-button/links-button.spec.ts +139 -0
  52. package/src/lib/shared/components/links-button/links-button.ts +45 -0
  53. package/src/lib/shared/components/paginator/paginator.css +0 -0
  54. package/src/lib/shared/components/paginator/paginator.html +10 -0
  55. package/src/lib/shared/components/paginator/paginator.spec.ts +23 -0
  56. package/src/lib/shared/components/paginator/paginator.ts +25 -0
  57. package/src/lib/shared/components/stepper/step/step.css +8 -0
  58. package/src/lib/shared/components/stepper/step/step.html +1 -0
  59. package/src/lib/shared/components/stepper/step/step.spec.ts +131 -0
  60. package/src/lib/shared/components/stepper/step/step.ts +14 -0
  61. package/src/lib/shared/components/stepper/stepper.css +17 -0
  62. package/src/lib/shared/components/stepper/stepper.html +11 -0
  63. package/src/lib/shared/components/stepper/stepper.spec.ts +238 -0
  64. package/src/lib/shared/components/stepper/stepper.ts +57 -0
  65. package/src/lib/shared/components/tab-group/tab-group.component.html +84 -0
  66. package/src/lib/shared/components/tab-group/tab-group.component.scss +151 -0
  67. package/src/lib/shared/components/tab-group/tab-group.spec.ts +22 -0
  68. package/src/lib/shared/components/tab-group/tab-group.ts +218 -0
  69. package/src/lib/shared/components/tab-panel/tab-content.directive.ts +7 -0
  70. package/src/lib/shared/components/tab-panel/tab-label.directive.ts +7 -0
  71. package/src/lib/shared/components/tab-panel/tab-panel.css +0 -0
  72. package/src/lib/shared/components/tab-panel/tab-panel.spec.ts +22 -0
  73. package/src/lib/shared/components/tab-panel/tab-panel.ts +31 -0
  74. package/src/lib/shared/components/time-picker/time-picker.css +84 -0
  75. package/src/lib/shared/components/time-picker/time-picker.html +21 -0
  76. package/src/lib/shared/components/time-picker/time-picker.spec.ts +23 -0
  77. package/src/lib/shared/components/time-picker/time-picker.ts +90 -0
  78. package/types/ngx-opalbytes-components.d.ts +70 -13
@@ -0,0 +1,335 @@
1
+ import { ComponentFixture, TestBed } from "@angular/core/testing";
2
+ import { CaoDropDownComponent } from "./drop-down";
3
+ import { By } from "@angular/platform-browser";
4
+ import { AbstractControl } from "@angular/forms";
5
+ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
6
+
7
+ describe("CaoDropDownComponent", () => {
8
+ let component: CaoDropDownComponent;
9
+ let fixture: ComponentFixture<CaoDropDownComponent>;
10
+
11
+ const mockOptions = [{ nome: "Item 1", icon: "home" }, { nome: "Item 2" }];
12
+
13
+ beforeEach(async () => {
14
+ await TestBed.configureTestingModule({
15
+ imports: [CaoDropDownComponent],
16
+ }).compileComponents();
17
+
18
+ fixture = TestBed.createComponent(CaoDropDownComponent);
19
+ component = fixture.componentInstance;
20
+ component.options = mockOptions;
21
+ component.textHeader = "Selecione";
22
+ });
23
+
24
+ afterEach(() => {
25
+ fixture.destroy();
26
+ });
27
+
28
+ it("deve criar o componente", () => {
29
+ fixture.detectChanges();
30
+ expect(component).toBeTruthy();
31
+ });
32
+
33
+ it("deve exibir o texto do cabeçalho quando nenhum item estiver selecionado", () => {
34
+ fixture.detectChanges();
35
+ const header = fixture.debugElement.query(By.css(".dropdown-header span")).nativeElement;
36
+ expect(header.textContent.trim()).toBe("Selecione");
37
+ });
38
+
39
+ it("deve alternar o dropdown ao clicar no cabeçalho", () => {
40
+ fixture.detectChanges();
41
+ const header = fixture.debugElement.query(By.css(".dropdown-header"));
42
+
43
+ // Primeiro clique para abrir
44
+ header.triggerEventHandler("click", new Event('click'));
45
+ fixture.detectChanges();
46
+
47
+ expect(component.isOpen).toBeTruthy();
48
+
49
+ // Verificar se a lista está visível
50
+ const list = fixture.debugElement.query(By.css(".dropdown-list"));
51
+ expect(list).toBeTruthy();
52
+
53
+ // Segundo clique para fechar
54
+ header.triggerEventHandler("click", new Event('click'));
55
+ fixture.detectChanges();
56
+
57
+ expect(component.isOpen).toBeFalsy();
58
+ });
59
+
60
+ it("deve exibir os itens da lista quando o dropdown estiver aberto", () => {
61
+ // Abrir dropdown ANTES do primeiro detectChanges
62
+ component.isOpen = true;
63
+ component["checkAvailableSpace"]();
64
+
65
+ fixture.detectChanges();
66
+
67
+ const items = fixture.debugElement.queryAll(By.css(".dropdown-item"));
68
+ expect(items.length).toBe(mockOptions.length);
69
+ expect(items[0].nativeElement.textContent).toContain("Item 1");
70
+ expect(items[1].nativeElement.textContent).toContain("Item 2");
71
+ });
72
+
73
+ it("deve exibir o ícone quando o item possuir um", () => {
74
+ // Abrir dropdown ANTES do primeiro detectChanges
75
+ component.isOpen = true;
76
+ component["checkAvailableSpace"]();
77
+
78
+ fixture.detectChanges();
79
+
80
+ const icons = fixture.debugElement.queryAll(By.css(".dropdown-icon"));
81
+ // Apenas o primeiro item tem ícone
82
+ expect(icons.length).toBe(1);
83
+ expect(icons[0].nativeElement.textContent.trim()).toBe("home");
84
+ });
85
+
86
+ it("deve selecionar um item e fechar o dropdown", () => {
87
+ const emitSpy = vi.spyOn(component.itemSelected, "emit");
88
+ const markAsTouchedSpy = vi.spyOn(component as any, "markAsTouched");
89
+
90
+ // Abrir dropdown ANTES do primeiro detectChanges
91
+ component.isOpen = true;
92
+ component["checkAvailableSpace"]();
93
+
94
+ fixture.detectChanges();
95
+
96
+ const items = fixture.debugElement.queryAll(By.css(".dropdown-item"));
97
+ items[0].triggerEventHandler("click", new Event('click'));
98
+ fixture.detectChanges();
99
+
100
+ expect(component.selectedItem).toEqual(mockOptions[0]);
101
+ expect(component.isOpen).toBeFalsy();
102
+ expect(emitSpy).toHaveBeenCalledWith(mockOptions[0]);
103
+ expect(markAsTouchedSpy).toHaveBeenCalled();
104
+ });
105
+
106
+ it("deve exibir o texto do item selecionado no cabeçalho", () => {
107
+ // Configurar o estado ANTES da primeira detecção
108
+ component.selectedItem = mockOptions[1];
109
+
110
+ // Agora detectar mudanças
111
+ fixture.detectChanges();
112
+
113
+ const header = fixture.debugElement.query(By.css(".dropdown-header span")).nativeElement;
114
+ expect(header.textContent.trim()).toBe("Item 2");
115
+ });
116
+
117
+ it("deve fechar o dropdown ao clicar fora do componente", () => {
118
+ // Abrir dropdown ANTES do primeiro detectChanges
119
+ component.isOpen = true;
120
+ component["checkAvailableSpace"]();
121
+
122
+ fixture.detectChanges();
123
+
124
+ // Simular clique fora
125
+ const clickEvent = new MouseEvent('click');
126
+ document.dispatchEvent(clickEvent);
127
+
128
+ // Angular precisa processar o evento assíncrono
129
+ fixture.detectChanges();
130
+
131
+ expect(component.isOpen).toBeFalsy();
132
+ });
133
+
134
+ it("não deve fechar o dropdown se o clique for dentro do componente", () => {
135
+ // Abrir dropdown ANTES do primeiro detectChanges
136
+ component.isOpen = true;
137
+ component["checkAvailableSpace"]();
138
+
139
+ fixture.detectChanges();
140
+
141
+ // Simular clique no container do dropdown
142
+ const container = fixture.debugElement.query(By.css(".dropdown-container"));
143
+ container.triggerEventHandler("click", new Event('click', { bubbles: true }));
144
+ fixture.detectChanges();
145
+
146
+ expect(component.isOpen).toBeTruthy();
147
+ });
148
+
149
+ it("deve setar o valor corretamente com writeValue para item existente", () => {
150
+ fixture.detectChanges();
151
+
152
+ component.writeValue("Item 2");
153
+ fixture.detectChanges();
154
+
155
+ expect(component.selectedItem).toEqual(mockOptions[1]);
156
+ });
157
+
158
+ it("não deve alterar selectedItem com writeValue para item inexistente", () => {
159
+ fixture.detectChanges();
160
+
161
+ component.selectedItem = mockOptions[0];
162
+ component.writeValue("Item inexistente");
163
+ fixture.detectChanges();
164
+
165
+ expect(component.selectedItem).toEqual(mockOptions[0]);
166
+ });
167
+
168
+ it("deve chamar onChange ao selecionar um item", () => {
169
+ fixture.detectChanges();
170
+
171
+ const onChangeSpy = vi.fn();
172
+ component.registerOnChange(onChangeSpy);
173
+
174
+ component.selectItem(mockOptions[0]);
175
+
176
+ // O componente está passando o objeto completo, não apenas o nome
177
+ expect(onChangeSpy).toHaveBeenCalledWith(mockOptions[0]);
178
+ });
179
+
180
+ it("deve chamar onTouched ao marcar como touched", () => {
181
+ fixture.detectChanges();
182
+
183
+ const onTouchedSpy = vi.fn();
184
+ component.registerOnTouched(onTouchedSpy);
185
+
186
+ // Chamar toggleDropdown para disparar touched
187
+ component.toggleDropdown();
188
+
189
+ expect(onTouchedSpy).toHaveBeenCalled();
190
+ });
191
+
192
+ it("deve retornar o nome do controle através do getter", () => {
193
+ fixture.detectChanges();
194
+
195
+ component.controlName = "dropdown";
196
+ expect(component.controlNameValue).toBe("dropdown");
197
+ });
198
+
199
+ it("deve retornar erro de campo obrigatório se control estiver touched e vazio", () => {
200
+ fixture.detectChanges();
201
+
202
+ const fakeControl = {
203
+ touched: true,
204
+ dirty: false,
205
+ hasError: (err: string) => err === "required",
206
+ get: () => null,
207
+ } as unknown as AbstractControl;
208
+
209
+ component.control = fakeControl;
210
+ component.selectedItem = undefined;
211
+ fixture.detectChanges();
212
+
213
+ expect(component.errorText).toBe("*Campo obrigatório");
214
+ });
215
+
216
+ it("deve retornar null no errorText se não houver erro", () => {
217
+ // Configurar ANTES de detectChanges
218
+ component.selectedItem = mockOptions[0];
219
+
220
+ const fakeControl = {
221
+ touched: false,
222
+ hasError: () => false,
223
+ get: () => null,
224
+ } as unknown as AbstractControl;
225
+
226
+ component.control = fakeControl;
227
+
228
+ fixture.detectChanges();
229
+
230
+ expect(component.errorText).toBeNull();
231
+ });
232
+
233
+ it("markAsTouched deve chamar onTouched e marcar controle como touched", () => {
234
+ fixture.detectChanges();
235
+
236
+ component.isTouched = false;
237
+
238
+ // Criar um spy para markAsTouched
239
+ const markAsTouchedSpy = vi.fn();
240
+ component.control = {
241
+ markAsTouched: markAsTouchedSpy,
242
+ } as any;
243
+
244
+ const onTouchedSpy = vi.fn();
245
+ component.registerOnTouched(onTouchedSpy);
246
+
247
+ component["markAsTouched"]();
248
+
249
+ expect(onTouchedSpy).toHaveBeenCalled();
250
+ expect(markAsTouchedSpy).toHaveBeenCalled();
251
+ expect(component.isTouched).toBeTruthy();
252
+ });
253
+
254
+ it("markAsTouched não deve chamar onTouched se já estiver tocado", () => {
255
+ fixture.detectChanges();
256
+
257
+ component.isTouched = true;
258
+ const onTouchedSpy = vi.fn();
259
+ component.registerOnTouched(onTouchedSpy);
260
+
261
+ component["markAsTouched"]();
262
+
263
+ expect(onTouchedSpy).not.toHaveBeenCalled();
264
+ });
265
+
266
+ describe("checkAvailableSpace", () => {
267
+ it('deve definir dropDirection como "up" se dropDirectionInput for "up"', () => {
268
+ fixture.detectChanges();
269
+
270
+ component.dropDirectionInput = "up";
271
+ component["checkAvailableSpace"]();
272
+ expect(component.dropDirection).toBe("up");
273
+ });
274
+
275
+ it('deve definir dropDirection como "up" se não houver espaço suficiente abaixo', () => {
276
+ fixture.detectChanges();
277
+
278
+ component.dropDirectionInput = "down";
279
+
280
+ // Mock do getBoundingClientRect
281
+ const mockRect = {
282
+ bottom: window.innerHeight - 100,
283
+ top: 0,
284
+ left: 0,
285
+ right: 0,
286
+ height: 50,
287
+ width: 200,
288
+ x: 0,
289
+ y: 0,
290
+ toJSON: () => ({})
291
+ };
292
+
293
+ // Mock do elemento
294
+ const mockElement = {
295
+ getBoundingClientRect: vi.fn(() => mockRect)
296
+ };
297
+
298
+ // Substituir o elemento nativo
299
+ component["_eref"].nativeElement = mockElement;
300
+
301
+ component["checkAvailableSpace"]();
302
+ expect(component.dropDirection).toBe("up");
303
+ });
304
+
305
+ it('deve definir dropDirection como "down" se houver espaço suficiente abaixo', () => {
306
+ fixture.detectChanges();
307
+
308
+ component.dropDirectionInput = "down";
309
+
310
+ // Mock do getBoundingClientRect
311
+ const mockRect = {
312
+ bottom: 100,
313
+ top: 0,
314
+ left: 0,
315
+ right: 0,
316
+ height: 50,
317
+ width: 200,
318
+ x: 0,
319
+ y: 0,
320
+ toJSON: () => ({})
321
+ };
322
+
323
+ // Mock do elemento
324
+ const mockElement = {
325
+ getBoundingClientRect: vi.fn(() => mockRect)
326
+ };
327
+
328
+ // Substituir o elemento nativo
329
+ component["_eref"].nativeElement = mockElement;
330
+
331
+ component["checkAvailableSpace"]();
332
+ expect(component.dropDirection).toBe("down");
333
+ });
334
+ });
335
+ });
@@ -0,0 +1,335 @@
1
+ import { CommonModule } from "@angular/common";
2
+ import {
3
+ Component,
4
+ ElementRef,
5
+ EventEmitter,
6
+ Input,
7
+ Output,
8
+ ViewEncapsulation,
9
+ forwardRef,
10
+ OnInit,
11
+ OnDestroy,
12
+ ChangeDetectorRef,
13
+ } from "@angular/core";
14
+ import {
15
+ AbstractControl,
16
+ ControlValueAccessor,
17
+ NG_VALUE_ACCESSOR,
18
+ ReactiveFormsModule,
19
+ FormControl,
20
+ } from "@angular/forms";
21
+ import { MatIconModule } from "@angular/material/icon";
22
+ import { MatTooltipModule } from "@angular/material/tooltip";
23
+
24
+ export interface ICaoDropDownOption {
25
+ id?: string | number;
26
+ nome: string;
27
+ icon?: string;
28
+ iconPosition?: string;
29
+ dataCy?: string;
30
+ }
31
+
32
+ @Component({
33
+ selector: "cao-drop-down",
34
+ templateUrl: "./drop-down.html",
35
+ styleUrl: "./drop-down.css",
36
+ encapsulation: ViewEncapsulation.None,
37
+ imports: [
38
+ MatIconModule,
39
+ CommonModule,
40
+ MatTooltipModule,
41
+ ReactiveFormsModule,
42
+ ],
43
+ providers: [
44
+ {
45
+ provide: NG_VALUE_ACCESSOR,
46
+ useExisting: forwardRef(() => CaoDropDownComponent),
47
+ multi: true,
48
+ },
49
+ ],
50
+ })
51
+ export class CaoDropDownComponent implements ControlValueAccessor, OnInit, OnDestroy {
52
+ @Input() textHeader = "SELECIONE UM ITEM";
53
+ @Input() options: ICaoDropDownOption[] = [];
54
+ @Input() dropDownClass = "";
55
+ @Input() styleDropDown: { [klass: string]: unknown } = {};
56
+ @Input() control?: AbstractControl | null;
57
+ @Input() controlName = "";
58
+ @Input() isDisabled = false;
59
+ @Input() tooltip = "";
60
+ @Input() dropDirectionInput: "up" | "down" = "down";
61
+ @Input() dataCy?: string = "";
62
+
63
+ @Input() trackByFn?: (index: number, item: ICaoDropDownOption) => unknown;
64
+ @Input() compareWith?: (option: unknown, value: unknown) => boolean;
65
+ @Input() ariaLabel?: string;
66
+ @Input() ariaLabelledBy?: string;
67
+ @Input() listAriaLabel?: string;
68
+
69
+ @Output() readonly itemSelected = new EventEmitter<ICaoDropDownOption>();
70
+
71
+ isOpen = false;
72
+ selectedItem?: ICaoDropDownOption;
73
+ filteredOptions: ICaoDropDownOption[] = [];
74
+ searchControl = new FormControl("");
75
+ isTouched = false;
76
+ isFocused = false;
77
+ dropDirection: string | undefined;
78
+ removeValidBorder = false;
79
+
80
+
81
+ focusedIndex = -1;
82
+
83
+ private _handleClickOutside = this.onClickOutside.bind(this);
84
+
85
+ constructor(
86
+ private _eref: ElementRef,
87
+ private cdr: ChangeDetectorRef
88
+ ) { }
89
+
90
+ ngOnInit(): void {
91
+ document.addEventListener("click", this._handleClickOutside, true);
92
+ this.filteredOptions = [...this.options];
93
+
94
+ this.searchControl.valueChanges.subscribe((value) => {
95
+ this.filterOptions(value || "");
96
+ this.focusedIndex = -1
97
+ });
98
+ }
99
+
100
+ ngOnDestroy(): void {
101
+ document.removeEventListener("click", this._handleClickOutside, true);
102
+ }
103
+
104
+ get activeDescendant(): string | null {
105
+ if (this.isOpen && this.focusedIndex >= 0 && this.filteredOptions[this.focusedIndex]) {
106
+ return `option-${this.dataCy}-${this.focusedIndex}`;
107
+ }
108
+ return null;
109
+ }
110
+
111
+ toggleDropdown(): void {
112
+ if (this.isDisabled) { return };
113
+
114
+ this.checkAvailableSpace();
115
+ this.isOpen = !this.isOpen;
116
+ this.isFocused = this.isOpen;
117
+ this.markAsTouched();
118
+
119
+ if (this.isOpen) {
120
+ this.searchControl.setValue("");
121
+ this.filteredOptions = [...this.options];
122
+ this.removeValidBorder = false;
123
+ this.focusedIndex = -1;
124
+
125
+
126
+ setTimeout(() => {
127
+ if (this.isOpen) {
128
+ const searchInput = this._eref.nativeElement.querySelector('.dropdown-search input');
129
+ if (searchInput) {
130
+ searchInput.focus();
131
+ }
132
+ }
133
+ });
134
+ } else {
135
+ this.focusedIndex = -1;
136
+ }
137
+
138
+ this.cdr.detectChanges();
139
+ }
140
+
141
+ selectItem(option: ICaoDropDownOption): void {
142
+ if (this.isDisabled) { return };
143
+
144
+ this.selectedItem = option;
145
+ this.isOpen = false;
146
+ this.focusedIndex = -1;
147
+ this.itemSelected.emit(option);
148
+ this.onChange(option);
149
+ this.markAsTouched();
150
+ this.cdr.detectChanges();
151
+ }
152
+
153
+ clear(): void {
154
+ this.selectedItem = undefined;
155
+ this.onChange(null);
156
+ this.cdr.detectChanges();
157
+ }
158
+
159
+ filterOptions(searchTerm: string) {
160
+ if (!searchTerm) {
161
+ this.filteredOptions = [...this.options];
162
+ return;
163
+ }
164
+ const lowerCaseSearchTerm = searchTerm.toLowerCase();
165
+ this.filteredOptions = this.options.filter(
166
+ (option) =>
167
+ option.nome.toLowerCase().includes(lowerCaseSearchTerm) ||
168
+ option.id?.toString().toLowerCase().includes(lowerCaseSearchTerm)
169
+ );
170
+ }
171
+
172
+ trackBy(index: number, item: ICaoDropDownOption): unknown {
173
+ return this.trackByFn ? this.trackByFn(index, item) : item.id;
174
+ }
175
+
176
+ private onClickOutside(event: MouseEvent): void {
177
+ if (!this._eref.nativeElement.contains(event.target)) {
178
+ if (this.isOpen) {
179
+ this.isOpen = false;
180
+ }
181
+ if (this.isFocused) {
182
+ this.isFocused = false;
183
+ }
184
+ this.focusedIndex = -1;
185
+ this.markAsTouched();
186
+ this.removeValidBorder = true;
187
+ this.cdr.detectChanges();
188
+ }
189
+ }
190
+
191
+ onArrowDown(event: Event): void {
192
+ if (this.isDisabled) { return };
193
+
194
+ event.preventDefault();
195
+ event.stopPropagation();
196
+
197
+ if (this.filteredOptions.length === 0) { return };
198
+
199
+ if (this.focusedIndex === -1) {
200
+
201
+ this.focusedIndex = 0;
202
+ } else {
203
+
204
+ this.focusedIndex = (this.focusedIndex + 1) % this.filteredOptions.length;
205
+ }
206
+
207
+ this.scrollIntoView();
208
+ this.cdr.detectChanges();
209
+ }
210
+
211
+ onArrowUp(event: Event): void {
212
+ if (this.isDisabled) { return };
213
+
214
+ event.preventDefault();
215
+ event.stopPropagation();
216
+
217
+ if (this.filteredOptions.length === 0) { return };
218
+
219
+ if (this.focusedIndex === -1) {
220
+
221
+ this.focusedIndex = this.filteredOptions.length - 1;
222
+ } else {
223
+
224
+ this.focusedIndex = this.focusedIndex <= 0
225
+ ? this.filteredOptions.length - 1
226
+ : this.focusedIndex - 1;
227
+ }
228
+
229
+ this.scrollIntoView();
230
+ this.cdr.detectChanges();
231
+ }
232
+
233
+ onSearchEnter(event: Event): void {
234
+ event.preventDefault();
235
+ event.stopPropagation();
236
+
237
+ if (this.focusedIndex >= 0 && this.focusedIndex < this.filteredOptions.length) {
238
+ this.selectItem(this.filteredOptions[this.focusedIndex]);
239
+ } else if (this.filteredOptions.length > 0) {
240
+
241
+ this.selectItem(this.filteredOptions[0]);
242
+ }
243
+ }
244
+
245
+ closeDropdown(): void {
246
+ if (this.isDisabled) { return };
247
+
248
+ this.isOpen = false;
249
+ this.focusedIndex = -1;
250
+ this.isFocused = false;
251
+ this.cdr.detectChanges();
252
+ }
253
+
254
+ private scrollIntoView(): void {
255
+ if (!this.isOpen || this.focusedIndex === -1) { return };
256
+
257
+ setTimeout(() => {
258
+ const element = document.getElementById(`option-${this.dataCy}-${this.focusedIndex}`);
259
+ if (element) {
260
+ element.scrollIntoView({
261
+ block: 'nearest',
262
+ behavior: 'smooth'
263
+ });
264
+ }
265
+ }, 0);
266
+ }
267
+
268
+ checkAvailableSpace(): void {
269
+ if (this.dropDirectionInput === "up") {
270
+ this.dropDirection = "up";
271
+ return;
272
+ }
273
+
274
+ const rect = this._eref.nativeElement.getBoundingClientRect();
275
+ const windowHeight = window.innerHeight;
276
+ const dropdownHeight = 200
277
+
278
+ const spaceBelow = windowHeight - rect.bottom;
279
+ this.dropDirection = spaceBelow < dropdownHeight ? "up" : "down";
280
+ }
281
+
282
+
283
+ writeValue(value: unknown): void {
284
+ if (value !== null && value !== undefined) {
285
+ const match = this.options.find((opt) =>
286
+ this.compareWith
287
+ ? this.compareWith(opt, value)
288
+ : opt.id === (typeof value === "object" && value ? (value as any).id : value)
289
+ );
290
+
291
+ if (match) {
292
+ this.selectedItem = match;
293
+ } else {
294
+ const matchByName = this.options.find((opt) => opt.nome === value);
295
+ if (matchByName) {
296
+ this.selectedItem = matchByName;
297
+ }
298
+ }
299
+ } else {
300
+ this.selectedItem = undefined;
301
+ }
302
+ this.cdr.detectChanges();
303
+ }
304
+
305
+ registerOnChange(fn: (value: unknown) => void): void {
306
+ this.onChange = fn;
307
+ }
308
+
309
+ registerOnTouched(fn: () => void): void {
310
+ this.onTouched = fn;
311
+ }
312
+
313
+ onChange: (value: unknown) => void = () => { };
314
+ onTouched = () => { };
315
+
316
+ get errorText(): string | null {
317
+ if (!this.control) { return null; }
318
+ if (this.control.hasError("required")) {
319
+ return "*Campo obrigatório";
320
+ }
321
+ return null;
322
+ }
323
+
324
+ private markAsTouched() {
325
+ if (!this.isTouched) {
326
+ this.onTouched();
327
+ this.isTouched = true;
328
+ this.control?.markAsTouched();
329
+ }
330
+ }
331
+
332
+ get controlNameValue(): string {
333
+ return this.controlName;
334
+ }
335
+ }
@@ -0,0 +1,29 @@
1
+ * {
2
+ list-style: none;
3
+ text-decoration: none;
4
+ }
5
+
6
+ .footer-component{
7
+ display: flex;
8
+ align-items: end;
9
+ justify-content: end;
10
+ height: 35px;
11
+ padding: 0 50px 10px 0;
12
+ position: fixed;
13
+ bottom: 0;
14
+ right: 0;
15
+ width: 100%;
16
+ z-index: 100;
17
+ }
18
+
19
+ .footer-component .flex{
20
+ display: flex;
21
+
22
+ }
23
+ .footer-component.isFixed{
24
+ position: fixed;
25
+ }
26
+
27
+ .footer-component.col{
28
+ flex-direction: column;
29
+ }
@@ -0,0 +1,10 @@
1
+ <footer class="footer-element" [class.fixed]="isFixed">
2
+ <div class="flex col">
3
+ <ng-content />
4
+ @if(footerText){
5
+ <ul class="footer-end">
6
+ <li>{{ footerText }} © {{ currentYear }}</li>
7
+ </ul>
8
+ }
9
+ </div>
10
+ </footer>