Skip to content

Commit 88fe97e

Browse files
committed
PER-10229-fix-archive-invite-dropdown
Implemented a workaround to allow a custom div/button to toggle the open state of a native <select> by adjusting its size attribute. This mimics dropdown behavior without relying on unsupported programmatic opening. Also handles closing on selection.
1 parent b1f6c08 commit 88fe97e

File tree

4 files changed

+176
-18
lines changed

4 files changed

+176
-18
lines changed

src/app/shared/components/form-input/form-input.component.html

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,24 +14,26 @@
1414
[disabled]="disabled"
1515
[readOnly]="disabled"
1616
/>
17-
<select
18-
*ngIf="type === 'select'"
19-
class="form-control input-vertical"
20-
[ngClass]="{ placeholder: !control.value }"
21-
[id]="fieldName"
22-
[name]="fieldName"
23-
[formControl]="control"
24-
>
25-
<option *ngFor="let option of selectOptions" [value]="option.value">
26-
{{ option.text }}
27-
</option>
28-
</select>
29-
<div
30-
class="input-vertical-select-placeholder"
31-
*ngIf="type === 'select'"
32-
[class.selected]="control.value"
33-
>
34-
{{ getOptionTextFromValue(control.value) || placeholder }}
17+
<div class="select-wrapper" *ngIf="type === 'select'">
18+
<select
19+
class="form-control input-vertical real-select"
20+
[ngClass]="{ placeholder: !control.value }"
21+
[id]="fieldName"
22+
[name]="fieldName"
23+
[formControl]="control"
24+
>
25+
<option *ngFor="let option of selectOptions" [value]="option.value">
26+
{{ option.text }}
27+
</option>
28+
</select>
29+
<label
30+
class="open-toggle input-vertical-select-placeholder"
31+
[attr.for]="fieldName"
32+
[class.selected]="control.value"
33+
(click)="openSelect()"
34+
>
35+
{{ getOptionTextFromValue(control.value) || placeholder }}
36+
</label>
3537
</div>
3638
<div class="input-vertical-error" [ngClass]="{ hidden: !errors }">
3739
{{ errors }}

src/app/shared/components/form-input/form-input.component.scss

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,41 @@
33
cursor: not-allowed;
44
user-select: none;
55
}
6+
7+
.select-wrapper {
8+
position: relative;
9+
width: 100%;
10+
border: 1px solid #ced4da;
11+
padding: 10px 0px;
12+
}
13+
14+
.real-select {
15+
position: absolute;
16+
top: 0;
17+
left: 0;
18+
width: 100%;
19+
height: 100%;
20+
opacity: 0;
21+
z-index: 2;
22+
cursor: pointer;
23+
}
24+
25+
.input-vertical-select-placeholder {
26+
position: relative;
27+
z-index: 1;
28+
}
29+
30+
.select-wrapper {
31+
position: relative;
32+
width: 100%;
33+
}
34+
35+
.styled-select {
36+
width: 100%;
37+
cursor: pointer;
38+
}
39+
40+
.open-toggle {
41+
cursor: pointer;
42+
top: 24px;
43+
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import { ComponentFixture, TestBed } from '@angular/core/testing';
2+
import {
3+
FormsModule,
4+
ReactiveFormsModule,
5+
UntypedFormControl,
6+
} from '@angular/forms';
7+
import { By } from '@angular/platform-browser';
8+
import {
9+
FormInputComponent,
10+
FormInputSelectOption,
11+
} from './form-input.component';
12+
13+
describe('FormInputComponent', () => {
14+
let component: FormInputComponent;
15+
let fixture: ComponentFixture<FormInputComponent>;
16+
17+
beforeEach(async () => {
18+
await TestBed.configureTestingModule({
19+
declarations: [FormInputComponent],
20+
imports: [FormsModule, ReactiveFormsModule],
21+
}).compileComponents();
22+
23+
fixture = TestBed.createComponent(FormInputComponent);
24+
component = fixture.componentInstance;
25+
});
26+
27+
it('should create', () => {
28+
expect(component).toBeTruthy();
29+
});
30+
31+
it('should render a text input when type is not "select"', () => {
32+
component.type = 'text';
33+
component.fieldName = 'username';
34+
component.placeholder = 'Enter username';
35+
component.control = new UntypedFormControl('');
36+
fixture.detectChanges();
37+
38+
const inputEl = fixture.debugElement.query(By.css('input.form-control'));
39+
40+
expect(inputEl).toBeTruthy();
41+
expect(inputEl.nativeElement.placeholder).toBe('Enter username');
42+
});
43+
44+
it('should render a select when type is "select"', () => {
45+
component.type = 'select';
46+
component.fieldName = 'country';
47+
component.placeholder = 'Select country';
48+
component.selectOptions = [
49+
{ value: 'us', text: 'USA' },
50+
{ value: 'ca', text: 'Canada' },
51+
];
52+
component.control = new UntypedFormControl('');
53+
fixture.detectChanges();
54+
55+
const selectEl = fixture.debugElement.query(By.css('select'));
56+
57+
expect(selectEl).toBeTruthy();
58+
const options = selectEl.queryAll(By.css('option'));
59+
60+
expect(options.length).toBe(2);
61+
});
62+
63+
it('should toggle openStatus when openSelect() is called', () => {
64+
expect(component.openStatus).toBeFalse();
65+
component.openSelect();
66+
67+
expect(component.openStatus).toBeTrue();
68+
component.openSelect();
69+
70+
expect(component.openStatus).toBeFalse();
71+
});
72+
73+
it('should reset openStatus and alert when handleChange() is called', () => {
74+
spyOn(window, 'alert');
75+
component.openStatus = true;
76+
component.handleChange();
77+
78+
expect(component.openStatus).toBeFalse();
79+
expect(window.alert).toHaveBeenCalledWith('Element selected... closed');
80+
});
81+
82+
it('should display the correct option text from value', () => {
83+
const options: FormInputSelectOption[] = [
84+
{ text: 'Apple', value: 'a' },
85+
{ text: 'Banana', value: 'b' },
86+
];
87+
component.selectOptions = options;
88+
const result = component.getOptionTextFromValue('b');
89+
90+
expect(result).toBe('Banana');
91+
});
92+
93+
it('should hide label if value is empty and type is number', () => {
94+
component.type = 'number';
95+
component.control = new UntypedFormControl('');
96+
97+
expect(component.isLabelHidden()).toBeTrue();
98+
});
99+
100+
it('should not hide label if type is date', () => {
101+
component.type = 'date';
102+
component.control = new UntypedFormControl('');
103+
104+
expect(component.isLabelHidden()).toBeFalse();
105+
});
106+
});

src/app/shared/components/form-input/form-input.component.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
ElementRef,
77
AfterViewInit,
88
HostBinding,
9+
ViewChild,
910
} from '@angular/core';
1011
import { UntypedFormControl } from '@angular/forms';
1112
import { getFormInputError } from '@shared/utilities/forms';
@@ -47,6 +48,8 @@ export class FormInputComponent implements OnInit, AfterViewInit {
4748
@HostBinding('class.right-align') rightAlign = false;
4849
@HostBinding('class.input-vertical') inputVertical = true;
4950

51+
openStatus = false;
52+
5053
@Input() config: FormInputConfig;
5154

5255
constructor(private element: ElementRef) {}
@@ -125,4 +128,13 @@ export class FormInputComponent implements OnInit, AfterViewInit {
125128
getOptionTextFromValue(value: string) {
126129
return find(this.selectOptions, { value })?.text;
127130
}
131+
132+
openSelect() {
133+
this.openStatus = !this.openStatus;
134+
}
135+
136+
handleChange() {
137+
this.openStatus = false;
138+
alert('Element selected... closed');
139+
}
128140
}

0 commit comments

Comments
 (0)