Skip to content

Commit eea25d0

Browse files
authored
fix(angular): item styling when control has value (#24932)
Resolves #23809
1 parent 982dc85 commit eea25d0

File tree

4 files changed

+52
-11
lines changed

4 files changed

+52
-11
lines changed

angular/src/directives/control-value-accessors/value-accessor.ts

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -110,13 +110,17 @@ export class ValueAccessor implements ControlValueAccessor, AfterViewInit, OnDes
110110

111111
export const setIonicClasses = (element: ElementRef): void => {
112112
raf(() => {
113-
const input = element.nativeElement as HTMLElement;
113+
const input = element.nativeElement as HTMLInputElement;
114+
const hasValue = input.value != null && input.value.toString().length > 0;
114115
const classes = getClasses(input);
115116
setClasses(input, classes);
116-
117117
const item = input.closest('ion-item');
118118
if (item) {
119-
setClasses(item, classes);
119+
if (hasValue) {
120+
setClasses(item, [...classes, 'item-has-value']);
121+
} else {
122+
setClasses(item, classes);
123+
}
120124
}
121125
});
122126
};
@@ -127,21 +131,18 @@ const getClasses = (element: HTMLElement) => {
127131
for (let i = 0; i < classList.length; i++) {
128132
const item = classList.item(i);
129133
if (item !== null && startsWith(item, 'ng-')) {
130-
classes.push(`ion-${item.substr(3)}`);
134+
classes.push(`ion-${item.substring(3)}`);
131135
}
132136
}
133137
return classes;
134138
};
135139

136140
const setClasses = (element: HTMLElement, classes: string[]) => {
137141
const classList = element.classList;
138-
['ion-valid', 'ion-invalid', 'ion-touched', 'ion-untouched', 'ion-dirty', 'ion-pristine'].forEach((c) =>
139-
classList.remove(c)
140-
);
141-
142-
classes.forEach((c) => classList.add(c));
142+
classList.remove('ion-valid', 'ion-invalid', 'ion-touched', 'ion-untouched', 'ion-dirty', 'ion-pristine');
143+
classList.add(...classes);
143144
};
144145

145146
const startsWith = (input: string, search: string): boolean => {
146-
return input.substr(0, search.length) === search;
147+
return input.substring(0, search.length) === search;
147148
};

angular/test/test-app/e2e/src/modal.spec.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ describe('Modals', () => {
4343

4444
});
4545

46-
4746
describe('Modals: Inline', () => {
4847
beforeEach(() => {
4948
cy.visit('/modal-inline');
@@ -77,3 +76,29 @@ describe('Modals: Inline', () => {
7776
cy.get('ion-modal').children('.ion-page').should('not.exist');
7877
})
7978
});
79+
80+
describe('when in a modal', () => {
81+
82+
beforeEach(() => {
83+
cy.visit('/modals');
84+
cy.get('#action-button').click();
85+
cy.get('#close-modal').click();
86+
cy.get('#action-button').click();
87+
});
88+
89+
it('should render ion-item item-has-value class when control value is set', () => {
90+
cy.get('[formControlName="select"]').invoke('attr', 'value', 0);
91+
cy.get('#inputWithFloatingLabel').should('have.class', 'item-has-value');
92+
});
93+
94+
it('should not render ion-item item-has-value class when control value is undefined', () => {
95+
cy.get('[formControlName="select"]').invoke('attr', 'value', undefined);
96+
cy.get('#inputWithFloatingLabel').should('not.have.class', 'item-has-value');
97+
});
98+
99+
it('should not render ion-item item-has-value class when control value is null', () => {
100+
cy.get('[formControlName="select"]').invoke('attr', 'value', null);
101+
cy.get('#inputWithFloatingLabel').should('not.have.class', 'item-has-value');
102+
});
103+
104+
});

angular/test/test-app/src/app/modal-example/modal-example.component.html

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,14 @@ <h3>{{valueFromParams}}</h3>
2222
<ion-button (click)="push()" class="push-page">Push page</ion-button>
2323
<ion-button (click)="pop()" class="pop-page">Pop page</ion-button>
2424
</p>
25+
26+
<form [formGroup]="form">
27+
<ion-item id="inputWithFloatingLabel">
28+
<ion-label color="primary" position="floating">Floating Label</ion-label>
29+
<ion-select multiple="false" formControlName="select">
30+
<ion-select-option [value]="0">Option 0</ion-select-option>
31+
<ion-select-option [value]="1">Option 1</ion-select-option>
32+
</ion-select>
33+
</ion-item>
34+
</form>
2535
</ion-content>

angular/test/test-app/src/app/modal-example/modal-example.component.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Component, Input, NgZone, OnInit, Optional } from '@angular/core';
2+
import { FormControl, FormGroup } from '@angular/forms';
23
import { ModalController, NavParams, IonNav, ViewWillLeave, ViewDidEnter, ViewDidLeave } from '@ionic/angular';
34

45
@Component({
@@ -9,6 +10,10 @@ export class ModalExampleComponent implements OnInit, ViewWillLeave, ViewDidEnte
910

1011
@Input() value: string;
1112

13+
form = new FormGroup({
14+
select: new FormControl([])
15+
});
16+
1217
valueFromParams: string;
1318
onInit = 0;
1419
willEnter = 0;

0 commit comments

Comments
 (0)