Skip to content

Commit 0e4dee8

Browse files
committed
fix(textarea): update element internals on load, value change, disabled and required change
1 parent 4ec7a7f commit 0e4dee8

File tree

2 files changed

+50
-18
lines changed

2 files changed

+50
-18
lines changed

core/src/components/textarea/test/form/index.html

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,17 @@
1212
<script src="../../../../../scripts/testing/scripts.js"></script>
1313
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
1414
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
15+
16+
<style>
17+
.required-label {
18+
display: none;
19+
}
20+
21+
.textarea-required .required-label {
22+
display: inline;
23+
color: red;
24+
}
25+
</style>
1526
</head>
1627

1728
<body>
@@ -26,32 +37,41 @@
2637
<form onsubmit="return onSubmit(event)">
2738
<ion-textarea
2839
fill="outline"
29-
label="Label"
3040
label-placement="stacked"
3141
placeholder="Placeholder"
3242
helper-text="Helper message"
3343
counter="true"
3444
maxlength="999"
35-
required
3645
>
46+
<div slot="label">Label <span class="required-label">(Required) *</span></div>
3747
<ion-icon slot="end" name="square-outline"></ion-icon>
3848
</ion-textarea>
3949

4050
<p>
41-
Clicking the button below should show a popup saying textarea is required in Chrome, Safari, and Firefox
42-
when the textarea is empty.
51+
Click the first button below to toggle the required prop and then click the submit button to attempt to
52+
submit the form. It should show a popup warning to fill out the field when textarea is required and empty.
53+
When the textarea is not required, or the field is filled, the form should submit by sending a console log.
4354
</p>
44-
<button>Submit</button>
55+
<button type="button" onClick="toggleRequired()">Toggle Required</button>
56+
<button type="submit">Submit</button>
4557
</form>
4658
</ion-content>
4759
</ion-app>
4860

4961
<script>
5062
function onSubmit(event) {
5163
event.preventDefault();
52-
console.log('Form submitted');
64+
const textarea = document.querySelector('ion-textarea');
65+
console.log('Form submitted with value:', textarea.value);
5366
return true;
5467
}
68+
69+
function toggleRequired() {
70+
const textarea = document.querySelector('ion-textarea');
71+
textarea.required = textarea.required ? false : true;
72+
textarea.classList.toggle('textarea-required', textarea.required);
73+
console.log('Textarea required set to:', textarea.required);
74+
}
5575
</script>
5676
</body>
5777
</html>

core/src/components/textarea/textarea.tsx

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,11 @@ export class Textarea implements ComponentInterface {
149149
*/
150150
@Prop() disabled = false;
151151

152+
@Watch('disabled')
153+
protected disabledChanged() {
154+
this.updateElementInternals();
155+
}
156+
152157
/**
153158
* The fill for the item. If `"solid"` the item will have a background. If
154159
* `"outline"` the item will be transparent with a border. Only available when the theme is `"md"`.
@@ -300,16 +305,23 @@ export class Textarea implements ComponentInterface {
300305
if (nativeInput && nativeInput.value !== value) {
301306
nativeInput.value = value;
302307
}
308+
this.updateElementInternals();
303309
this.runAutoGrow();
304-
this.reportValidity();
305310
}
306311

307312
/**
308313
* Update validation state when required prop changes
309314
*/
310315
@Watch('required')
311316
protected requiredChanged() {
312-
this.reportValidity();
317+
// Explicitly update the native element's required attribute to ensure
318+
// browser validation works correctly when required changes dynamically.
319+
// While the template binding should handle this, we need to update it
320+
// synchronously for the browser's validation to recognize the change.
321+
if (this.nativeInput) {
322+
this.nativeInput.required = this.required;
323+
}
324+
this.updateElementInternals();
313325
}
314326

315327
/**
@@ -454,8 +466,8 @@ export class Textarea implements ComponentInterface {
454466

455467
componentDidLoad() {
456468
this.originalIonInput = this.ionInput;
469+
this.updateElementInternals();
457470
this.runAutoGrow();
458-
this.reportValidity();
459471
}
460472

461473
componentDidRender() {
@@ -578,11 +590,16 @@ export class Textarea implements ComponentInterface {
578590
}
579591

580592
/**
581-
* Reports the validity state to the browser via ElementInternals.
582-
* This delegates to the native textarea's built-in validation,
583-
* which automatically handles the required prop and other constraints.
593+
* Updates the form value and reports validity state to the browser via
594+
* ElementInternals. This should be called when the component loads, when
595+
* the required prop changes, when the disabled prop changes, and when the value
596+
* changes to ensure the form value stays in sync and validation state is updated.
584597
*/
585-
private reportValidity() {
598+
private updateElementInternals() {
599+
// Disabled form controls should not be included in form data
600+
// Pass null to setFormValue when disabled to exclude it from form submission
601+
const value = this.disabled ? null : this.getValue();
602+
this.internals.setFormValue(value);
586603
reportValidityToElementInternals(this.nativeInput, this.internals);
587604
}
588605

@@ -600,11 +617,6 @@ export class Textarea implements ComponentInterface {
600617
};
601618

602619
private onChange = (ev: Event) => {
603-
const input = ev.target as HTMLTextAreaElement | null;
604-
if (input) {
605-
this.internals.setFormValue(input.value);
606-
this.reportValidity();
607-
}
608620
this.emitValueChange(ev);
609621
};
610622

0 commit comments

Comments
 (0)