Skip to content

Commit 55735df

Browse files
feat(textarea): reflect disabled and readonly props (#30910)
Co-authored-by: Maria Hutt <maria.hutt@outsystems.com>
1 parent 46806bd commit 55735df

File tree

14 files changed

+532
-206
lines changed

14 files changed

+532
-206
lines changed

core/api.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1876,7 +1876,7 @@ ion-textarea,prop,cols,number | undefined,undefined,false,true
18761876
ion-textarea,prop,counter,boolean,false,false,false
18771877
ion-textarea,prop,counterFormatter,((inputLength: number, maxLength: number) => string) | undefined,undefined,false,false
18781878
ion-textarea,prop,debounce,number | undefined,undefined,false,false
1879-
ion-textarea,prop,disabled,boolean,false,false,false
1879+
ion-textarea,prop,disabled,boolean,false,false,true
18801880
ion-textarea,prop,enterkeyhint,"done" | "enter" | "go" | "next" | "previous" | "search" | "send" | undefined,undefined,false,false
18811881
ion-textarea,prop,errorText,string | undefined,undefined,false,false
18821882
ion-textarea,prop,fill,"outline" | "solid" | undefined,undefined,false,false
@@ -1889,7 +1889,7 @@ ion-textarea,prop,minlength,number | undefined,undefined,false,false
18891889
ion-textarea,prop,mode,"ios" | "md",undefined,false,false
18901890
ion-textarea,prop,name,string,this.inputId,false,false
18911891
ion-textarea,prop,placeholder,string | undefined,undefined,false,false
1892-
ion-textarea,prop,readonly,boolean,false,false,false
1892+
ion-textarea,prop,readonly,boolean,false,false,true
18931893
ion-textarea,prop,required,boolean,false,false,false
18941894
ion-textarea,prop,rows,number | undefined,undefined,false,false
18951895
ion-textarea,prop,shape,"round" | undefined,undefined,false,false

core/src/components/textarea/textarea.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ export class Textarea implements ComponentInterface {
132132
/**
133133
* If `true`, the user cannot interact with the textarea.
134134
*/
135-
@Prop() disabled = false;
135+
@Prop({ reflect: true }) disabled = false;
136136

137137
/**
138138
* The fill for the item. If `"solid"` the item will have a background. If
@@ -177,7 +177,7 @@ export class Textarea implements ComponentInterface {
177177
/**
178178
* If `true`, the user cannot modify the value.
179179
*/
180-
@Prop() readonly = false;
180+
@Prop({ reflect: true }) readonly = false;
181181

182182
/**
183183
* If `true`, the user must fill in a value before submitting a form.

packages/angular/test/base/e2e/src/lazy/inputs.spec.ts

Lines changed: 116 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -5,105 +5,126 @@ test.describe('Inputs', () => {
55
await page.goto('/lazy/inputs');
66
});
77

8-
test('should have default values', async ({ page }) => {
9-
// Check primary elements for default values
10-
await expect(page.locator('ion-checkbox').first()).toHaveJSProperty('checked', true);
11-
await expect(page.locator('ion-radio-group').first()).toHaveJSProperty('value', 'nes');
12-
await expect(page.locator('ion-toggle').first()).toHaveJSProperty('checked', true);
13-
await expect(page.locator('ion-input').first()).toHaveJSProperty('value', 'some text');
14-
await expect(page.locator('ion-input-otp').first()).toHaveJSProperty('value', '1234');
15-
await expect(page.locator('ion-datetime').first()).toHaveJSProperty('value', '1994-03-15');
16-
await expect(page.locator('ion-select').first()).toHaveJSProperty('value', 'nes');
17-
await expect(page.locator('ion-range').first()).toHaveJSProperty('value', 50);
18-
});
8+
test.describe('basic functionality', () => {
9+
test('should have default values', async ({ page }) => {
10+
// Check primary elements for default values
11+
await expect(page.locator('ion-checkbox').first()).toHaveJSProperty('checked', true);
12+
await expect(page.locator('ion-radio-group').first()).toHaveJSProperty('value', 'nes');
13+
await expect(page.locator('ion-toggle').first()).toHaveJSProperty('checked', true);
14+
await expect(page.locator('ion-input').first()).toHaveJSProperty('value', 'some text');
15+
await expect(page.locator('ion-input-otp').first()).toHaveJSProperty('value', '1234');
16+
await expect(page.locator('ion-datetime').first()).toHaveJSProperty('value', '1994-03-15');
17+
await expect(page.locator('ion-select').first()).toHaveJSProperty('value', 'nes');
18+
await expect(page.locator('ion-range').first()).toHaveJSProperty('value', 50);
19+
});
1920

20-
test('should reset values', async ({ page }) => {
21-
await page.locator('#reset-button').click();
22-
23-
// Check primary elements after reset
24-
await expect(page.locator('ion-checkbox').first()).toHaveJSProperty('checked', false);
25-
await expect(page.locator('ion-radio-group').first()).toHaveJSProperty('value', undefined);
26-
await expect(page.locator('ion-toggle').first()).toHaveJSProperty('checked', false);
27-
/**
28-
* The `value` property gets set to undefined
29-
* for these components, so we need to check
30-
* that the value property is undefined.
31-
*/
32-
await expect(page.locator('ion-input').first()).toHaveJSProperty('value', undefined);
33-
await expect(page.locator('ion-input-otp').first()).toHaveJSProperty('value', undefined);
34-
await expect(page.locator('ion-datetime').first()).toHaveJSProperty('value', undefined);
35-
await expect(page.locator('ion-select').first()).toHaveJSProperty('value', undefined);
36-
await expect(page.locator('ion-range').first()).toHaveJSProperty('value', undefined);
37-
});
21+
test('should reset values', async ({ page }) => {
22+
await page.locator('#reset-button').click();
23+
24+
// Check primary elements after reset
25+
await expect(page.locator('ion-checkbox').first()).toHaveJSProperty('checked', false);
26+
await expect(page.locator('ion-radio-group').first()).toHaveJSProperty('value', undefined);
27+
await expect(page.locator('ion-toggle').first()).toHaveJSProperty('checked', false);
28+
/**
29+
* The `value` property gets set to undefined
30+
* for these components, so we need to check
31+
* that the value property is undefined.
32+
*/
33+
await expect(page.locator('ion-input').first()).toHaveJSProperty('value', undefined);
34+
await expect(page.locator('ion-input-otp').first()).toHaveJSProperty('value', undefined);
35+
await expect(page.locator('ion-datetime').first()).toHaveJSProperty('value', undefined);
36+
await expect(page.locator('ion-select').first()).toHaveJSProperty('value', undefined);
37+
await expect(page.locator('ion-range').first()).toHaveJSProperty('value', undefined);
38+
});
3839

39-
test('should set values', async ({ page }) => {
40-
await page.locator('#reset-button').click();
41-
await page.locator('#set-button').click();
42-
43-
// Check primary elements after setting values
44-
await expect(page.locator('ion-checkbox').first()).toHaveJSProperty('checked', true);
45-
await expect(page.locator('ion-radio-group').first()).toHaveJSProperty('value', 'nes');
46-
await expect(page.locator('ion-toggle').first()).toHaveJSProperty('checked', true);
47-
await expect(page.locator('ion-input').first()).toHaveJSProperty('value', 'some text');
48-
await expect(page.locator('ion-input-otp').first()).toHaveJSProperty('value', '1234');
49-
await expect(page.locator('ion-datetime').first()).toHaveJSProperty('value', '1994-03-15');
50-
await expect(page.locator('ion-select').first()).toHaveJSProperty('value', 'nes');
51-
await expect(page.locator('ion-range').first()).toHaveJSProperty('value', 50);
52-
});
40+
test('should set values', async ({ page }) => {
41+
await page.locator('#reset-button').click();
42+
await page.locator('#set-button').click();
43+
44+
// Check primary elements after setting values
45+
await expect(page.locator('ion-checkbox').first()).toHaveJSProperty('checked', true);
46+
await expect(page.locator('ion-radio-group').first()).toHaveJSProperty('value', 'nes');
47+
await expect(page.locator('ion-toggle').first()).toHaveJSProperty('checked', true);
48+
await expect(page.locator('ion-input').first()).toHaveJSProperty('value', 'some text');
49+
await expect(page.locator('ion-input-otp').first()).toHaveJSProperty('value', '1234');
50+
await expect(page.locator('ion-datetime').first()).toHaveJSProperty('value', '1994-03-15');
51+
await expect(page.locator('ion-select').first()).toHaveJSProperty('value', 'nes');
52+
await expect(page.locator('ion-range').first()).toHaveJSProperty('value', 50);
53+
});
5354

54-
test('should update angular when values change', async ({ page }) => {
55-
await page.locator('#reset-button').click();
56-
57-
await page.locator('ion-checkbox#first-checkbox').click();
58-
await page.locator('ion-radio').first().click();
59-
await page.locator('ion-toggle').first().click();
60-
61-
await page.locator('ion-input').nth(0).locator('input').fill('hola');
62-
await page.locator('ion-input').nth(0).locator('input').blur();
63-
64-
await page.locator('ion-input-otp input').nth(0).fill('1');
65-
await page.locator('ion-input-otp input').nth(1).fill('2');
66-
await page.locator('ion-input-otp input').nth(2).fill('3');
67-
await page.locator('ion-input-otp input').nth(3).fill('4');
68-
await page.locator('ion-input-otp input').nth(3).blur();
69-
70-
// Set date to 1994-03-14
71-
await page.locator('ion-datetime').first().click();
72-
await page.locator('ion-datetime').first().locator('.calendar-day:not([disabled])').first().click();
73-
74-
await page.locator('ion-select#game-console').click();
75-
await expect(page.locator('ion-alert')).toBeVisible();
76-
// Playstation option
77-
await page.locator('ion-alert .alert-radio-button').nth(3).click();
78-
// Click confirm button
79-
await page.locator('ion-alert .alert-button:not(.alert-button-role-cancel)').click();
80-
81-
// Check note text (Angular binding updates)
82-
await expect(page.locator('#checkbox-note')).toHaveText('true');
83-
await expect(page.locator('#radio-note')).toHaveText('nes');
84-
await expect(page.locator('#toggle-note')).toHaveText('true');
85-
await expect(page.locator('#input-note')).toHaveText('hola');
86-
await expect(page.locator('#input-otp-note')).toHaveText('1234');
87-
await expect(page.locator('#datetime-note')).toHaveText('1994-03-14');
88-
await expect(page.locator('#select-note')).toHaveText('ps');
89-
});
55+
test('should update angular when values change', async ({ page }) => {
56+
await page.locator('#reset-button').click();
9057

91-
test('should update values when erasing input', async ({ page }) => {
92-
// Focus the input and press backspace to remove last character
93-
await page.locator('ion-input').nth(0).locator('input').click();
94-
await page.locator('ion-input').nth(0).locator('input').press('Backspace');
95-
// Check mirror element reflects the change
96-
await expect(page.locator('ion-input').nth(1)).toHaveJSProperty('value', 'some tex');
97-
// Check note text (Angular binding)
98-
await expect(page.locator('#input-note')).toHaveText('some tex');
99-
100-
// Focus the last OTP input and press backspace
101-
await page.locator('ion-input-otp input').last().click();
102-
await page.locator('ion-input-otp input').last().press('Backspace');
103-
// Check mirror element reflects the change
104-
await expect(page.locator('ion-input-otp').nth(1)).toHaveJSProperty('value', '123');
105-
// Check note text (Angular binding)
106-
await expect(page.locator('#input-otp-note')).toHaveText('123');
58+
await page.locator('ion-checkbox#first-checkbox').click();
59+
await page.locator('ion-radio').first().click();
60+
await page.locator('ion-toggle').first().click();
61+
62+
await page.locator('ion-input').nth(0).locator('input').fill('hola');
63+
await page.locator('ion-input').nth(0).locator('input').blur();
64+
65+
await page.locator('ion-input-otp input').nth(0).fill('1');
66+
await page.locator('ion-input-otp input').nth(1).fill('2');
67+
await page.locator('ion-input-otp input').nth(2).fill('3');
68+
await page.locator('ion-input-otp input').nth(3).fill('4');
69+
await page.locator('ion-input-otp input').nth(3).blur();
70+
71+
// Set date to 1994-03-14
72+
await page.locator('ion-datetime').first().click();
73+
await page.locator('ion-datetime').first().locator('.calendar-day:not([disabled])').first().click();
74+
75+
await page.locator('ion-select#game-console').click();
76+
await expect(page.locator('ion-alert')).toBeVisible();
77+
// Playstation option
78+
await page.locator('ion-alert .alert-radio-button').nth(3).click();
79+
// Click confirm button
80+
await page.locator('ion-alert .alert-button:not(.alert-button-role-cancel)').click();
81+
82+
// Check note text (Angular binding updates)
83+
await expect(page.locator('#checkbox-note')).toHaveText('true');
84+
await expect(page.locator('#radio-note')).toHaveText('nes');
85+
await expect(page.locator('#toggle-note')).toHaveText('true');
86+
await expect(page.locator('#input-note')).toHaveText('hola');
87+
await expect(page.locator('#input-otp-note')).toHaveText('1234');
88+
await expect(page.locator('#datetime-note')).toHaveText('1994-03-14');
89+
await expect(page.locator('#select-note')).toHaveText('ps');
90+
});
91+
92+
test('should update values when erasing input', async ({ page }) => {
93+
// Focus the input and press backspace to remove last character
94+
await page.locator('ion-input').nth(0).locator('input').click();
95+
await page.locator('ion-input').nth(0).locator('input').press('Backspace');
96+
// Check mirror element reflects the change
97+
await expect(page.locator('ion-input').nth(1)).toHaveJSProperty('value', 'some tex');
98+
// Check note text (Angular binding)
99+
await expect(page.locator('#input-note')).toHaveText('some tex');
100+
101+
// Focus the last OTP input and press backspace
102+
await page.locator('ion-input-otp input').last().click();
103+
await page.locator('ion-input-otp input').last().press('Backspace');
104+
// Check mirror element reflects the change
105+
await expect(page.locator('ion-input-otp').nth(1)).toHaveJSProperty('value', '123');
106+
// Check note text (Angular binding)
107+
await expect(page.locator('#input-otp-note')).toHaveText('123');
108+
});
109+
110+
test('should reflect props when component has a default value', async ({ page }) => {
111+
// Disable inputs
112+
await page.locator('#disable-button').click();
113+
114+
// Disabled prop
115+
await expect(page.locator('ion-input').first()).toHaveAttribute('disabled', '');
116+
await expect(page.locator('ion-input-otp').first()).toHaveAttribute('disabled', '');
117+
await expect(page.locator('ion-textarea').first()).toHaveAttribute('disabled', '');
118+
119+
// Reset disabled state and set readonly state
120+
await page.locator('#disable-button').click();
121+
await page.locator('#readonly-button').click();
122+
123+
// Readonly prop
124+
await expect(page.locator('ion-input').first()).toHaveAttribute('readonly', '');
125+
await expect(page.locator('ion-input-otp').first()).toHaveAttribute('readonly', '');
126+
await expect(page.locator('ion-textarea').first()).toHaveAttribute('readonly', '');
127+
});
107128
});
108129

109130
test.describe('updating text input refs', () => {
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { test, expect } from '@playwright/test';
2+
3+
test.describe('Inputs', () => {
4+
test.beforeEach(async ({ page }) => {
5+
await page.goto('/standalone/inputs');
6+
});
7+
8+
test.describe('basic functionality', () => {
9+
test('should reflect props when component has a default value', async ({ page }) => {
10+
// Disable inputs
11+
await page.locator('#disable-button').click();
12+
13+
// Disabled prop
14+
await expect(page.locator('ion-input')).toHaveAttribute('disabled', '');
15+
await expect(page.locator('ion-input-otp')).toHaveAttribute('disabled', '');
16+
await expect(page.locator('ion-textarea')).toHaveAttribute('disabled', '');
17+
18+
// Reset disabled state and set readonly state
19+
await page.locator('#disable-button').click();
20+
await page.locator('#readonly-button').click();
21+
22+
// Readonly prop
23+
await expect(page.locator('ion-input')).toHaveAttribute('readonly', '');
24+
await expect(page.locator('ion-input-otp')).toHaveAttribute('readonly', '');
25+
await expect(page.locator('ion-textarea')).toHaveAttribute('readonly', '');
26+
});
27+
});
28+
});

packages/angular/test/base/src/app/lazy/inputs/inputs.component.html

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,18 @@
1111

1212
<ion-item>
1313
<ion-label>DateTime</ion-label>
14-
<ion-datetime [(ngModel)]="datetime" min="1994-03-14" max="2017-12-09" display-format="MM/DD/YYYY"></ion-datetime>
14+
<ion-datetime [(ngModel)]="datetime" min="1994-03-14" max="2017-12-09" display-format="MM/DD/YYYY" [disabled]="isDisabled" [readonly]="isReadonly"></ion-datetime>
1515
<ion-note slot="end" id="datetime-note">{{datetime}}</ion-note>
1616
</ion-item>
1717

1818
<ion-item color="dark">
1919
<ion-label>DateTime Mirror</ion-label>
20-
<ion-datetime [(ngModel)]="datetime" min="1994-03-14" max="2017-12-09" display-format="MM/DD/YYYY"></ion-datetime>
20+
<ion-datetime [(ngModel)]="datetime" min="1994-03-14" max="2017-12-09" display-format="MM/DD/YYYY" [disabled]="isDisabled" [readonly]="isReadonly"></ion-datetime>
2121
<ion-note slot="end">{{datetime}}</ion-note>
2222
</ion-item>
2323

2424
<ion-item>
25-
<ion-select label="Select" [(ngModel)]="select" id="game-console">
25+
<ion-select label="Select" [(ngModel)]="select" id="game-console" [disabled]="isDisabled">
2626
<ion-select-option value="">No Game Console</ion-select-option>
2727
<ion-select-option value="nes">NES</ion-select-option>
2828
<ion-select-option value="n64" selected>Nintendo64</ion-select-option>
@@ -48,7 +48,7 @@
4848
</ion-item>
4949

5050
<ion-item>
51-
<ion-toggle [(ngModel)]="toggle" slot="end">
51+
<ion-toggle [(ngModel)]="toggle" slot="end" [disabled]="isDisabled">
5252
Toggle
5353
</ion-toggle>
5454
<ion-note slot="end" id="toggle-note">{{toggle}}</ion-note>
@@ -62,27 +62,27 @@
6262
</ion-item>
6363

6464
<ion-item>
65-
<ion-input label="Input" [(ngModel)]="input"></ion-input>
65+
<ion-input label="Input" [(ngModel)]="input" [disabled]="isDisabled" [readonly]="isReadonly"></ion-input>
6666
<ion-note slot="end" id="input-note">{{input}}</ion-note>
6767
</ion-item>
6868

6969
<ion-item color="dark">
70-
<ion-input label="Input Mirror" [(ngModel)]="input"></ion-input>
70+
<ion-input label="Input Mirror" [(ngModel)]="input" [disabled]="isDisabled" [readonly]="isReadonly"></ion-input>
7171
<ion-note slot="end">{{input}}</ion-note>
7272
</ion-item>
7373

7474
<ion-item>
75-
<ion-input-otp [(ngModel)]="inputOtp">Input OTP</ion-input-otp>
75+
<ion-input-otp [(ngModel)]="inputOtp" [disabled]="isDisabled" [readonly]="isReadonly">Input OTP</ion-input-otp>
7676
<ion-note slot="end" id="input-otp-note">{{inputOtp}}</ion-note>
7777
</ion-item>
7878

7979
<ion-item color="dark">
80-
<ion-input-otp [(ngModel)]="inputOtp">Input OTP Mirror</ion-input-otp>
80+
<ion-input-otp [(ngModel)]="inputOtp" [disabled]="isDisabled" [readonly]="isReadonly">Input OTP Mirror</ion-input-otp>
8181
<ion-note slot="end">{{inputOtp}}</ion-note>
8282
</ion-item>
8383

8484
<ion-item>
85-
<ion-checkbox [(ngModel)]="checkbox" slot="start" id="first-checkbox">
85+
<ion-checkbox [(ngModel)]="checkbox" slot="start" id="first-checkbox" [disabled]="isDisabled">
8686
Checkbox
8787
</ion-checkbox>
8888
<ion-note slot="end" id="checkbox-note">{{checkbox}}</ion-note>
@@ -97,7 +97,7 @@
9797

9898
<ion-item>
9999
<ion-radio-group value="nes" [(ngModel)]="radio" id="first-radio">
100-
<ion-radio value="nes">Radio</ion-radio>
100+
<ion-radio value="nes" [disabled]="isDisabled">Radio</ion-radio>
101101
</ion-radio-group>
102102
<ion-note slot="end" id="radio-note">{{radio}}</ion-note>
103103
</ion-item>
@@ -110,14 +110,20 @@
110110
</ion-item>
111111

112112
<ion-item>
113-
<ion-range [(ngModel)]="range" min="0" max="100" id="first-range">
113+
<ion-range [(ngModel)]="range" min="0" max="100" id="first-range" [disabled]="isDisabled">
114114
<ion-label slot="start">Range</ion-label>
115115
</ion-range>
116116
</ion-item>
117117

118+
<ion-item>
119+
<ion-textarea [(ngModel)]="textarea" minLength="0" maxLength="100" [disabled]="isDisabled" [readonly]="isReadonly" label="Textarea"></ion-textarea>
120+
</ion-item>
121+
118122
</ion-list>
119123
<p>
120124
<ion-button (click)="setValues()" id="set-button">Set values</ion-button>
121125
<ion-button (click)="resetValues()" id="reset-button">Reset values</ion-button>
126+
<ion-button (click)="toggleDisable()" id="disable-button">Toggle Disabled</ion-button>
127+
<ion-button (click)="toggleReadonly()" id="readonly-button">Toggle Readonly</ion-button>
122128
</p>
123129
</ion-content>

0 commit comments

Comments
 (0)