@ionic/core 8.7.4-dev.11755809082.11a7702b → 8.7.4-nightly.20250822

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.
@@ -43,10 +43,6 @@ const Input = /*@__PURE__*/ proxyCustomElement(class Input extends HTMLElement {
43
43
  * is applied in both cases.
44
44
  */
45
45
  this.hasFocus = false;
46
- /**
47
- * Track validation state for proper aria-live announcements
48
- */
49
- this.isInvalid = false;
50
46
  /**
51
47
  * Indicates whether and how the text value should be automatically capitalized as it is entered/edited by the user.
52
48
  * Available options: `"off"`, `"none"`, `"on"`, `"sentences"`, `"words"`, `"characters"`.
@@ -132,19 +128,6 @@ const Input = /*@__PURE__*/ proxyCustomElement(class Input extends HTMLElement {
132
128
  }
133
129
  this.didInputClearOnEdit = false;
134
130
  this.ionBlur.emit(ev);
135
- /**
136
- * Check validation state after blur to handle framework-managed classes.
137
- * Frameworks like Angular update classes asynchronously, often using
138
- * requestAnimationFrame or promises. Using setTimeout ensures we check
139
- * after all microtasks and animation frames have completed.
140
- */
141
- setTimeout(() => {
142
- const newIsInvalid = this.checkValidationState();
143
- if (this.isInvalid !== newIsInvalid) {
144
- this.isInvalid = newIsInvalid;
145
- forceUpdate(this);
146
- }
147
- }, 100);
148
131
  };
149
132
  this.onFocus = (ev) => {
150
133
  this.hasFocus = true;
@@ -244,42 +227,10 @@ const Input = /*@__PURE__*/ proxyCustomElement(class Input extends HTMLElement {
244
227
  componentWillLoad() {
245
228
  this.inheritedAttributes = Object.assign(Object.assign({}, inheritAriaAttributes(this.el)), inheritAttributes(this.el, ['tabindex', 'title', 'data-form-type', 'dir']));
246
229
  }
247
- /**
248
- * Checks if the input is in an invalid state based on validation classes
249
- */
250
- checkValidationState() {
251
- // Check for both Ionic and Angular validation classes on the element itself
252
- // Angular applies ng-touched/ng-invalid directly to the host element with ngModel
253
- const hasIonTouched = this.el.classList.contains('ion-touched');
254
- const hasIonInvalid = this.el.classList.contains('ion-invalid');
255
- const hasNgTouched = this.el.classList.contains('ng-touched');
256
- const hasNgInvalid = this.el.classList.contains('ng-invalid');
257
- // Return true if we have both touched and invalid states from either framework
258
- const isTouched = hasIonTouched || hasNgTouched;
259
- const isInvalid = hasIonInvalid || hasNgInvalid;
260
- return isTouched && isInvalid;
261
- }
262
230
  connectedCallback() {
263
231
  const { el } = this;
264
232
  this.slotMutationController = createSlotMutationController(el, ['label', 'start', 'end'], () => forceUpdate(this));
265
233
  this.notchController = createNotchController(el, () => this.notchSpacerEl, () => this.labelSlot);
266
- // Watch for class changes to update validation state
267
- if (Build.isBrowser && typeof MutationObserver !== 'undefined') {
268
- this.validationObserver = new MutationObserver(() => {
269
- const newIsInvalid = this.checkValidationState();
270
- if (this.isInvalid !== newIsInvalid) {
271
- this.isInvalid = newIsInvalid;
272
- // Force a re-render to update aria-describedby immediately
273
- forceUpdate(this);
274
- }
275
- });
276
- this.validationObserver.observe(el, {
277
- attributes: true,
278
- attributeFilter: ['class'],
279
- });
280
- }
281
- // Always set initial state
282
- this.isInvalid = this.checkValidationState();
283
234
  this.debounceChanged();
284
235
  if (Build.isBrowser) {
285
236
  document.dispatchEvent(new CustomEvent('ionInputDidLoad', {
@@ -316,11 +267,6 @@ const Input = /*@__PURE__*/ proxyCustomElement(class Input extends HTMLElement {
316
267
  this.notchController.destroy();
317
268
  this.notchController = undefined;
318
269
  }
319
- // Clean up validation observer to prevent memory leaks
320
- if (this.validationObserver) {
321
- this.validationObserver.disconnect();
322
- this.validationObserver = undefined;
323
- }
324
270
  }
325
271
  /**
326
272
  * Sets focus on the native `input` in `ion-input`. Use this method instead of the global
@@ -422,15 +368,15 @@ const Input = /*@__PURE__*/ proxyCustomElement(class Input extends HTMLElement {
422
368
  * Renders the helper text or error text values
423
369
  */
424
370
  renderHintText() {
425
- const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
371
+ const { helperText, errorText, helperTextId, errorTextId } = this;
426
372
  return [
427
- helperText && (h("div", { id: helperTextId, class: "helper-text", "aria-live": "polite" }, helperText)),
428
- errorText && (h("div", { id: errorTextId, class: "error-text", "aria-live": "assertive", "aria-atomic": "true", role: "alert", style: { display: isInvalid ? 'block' : 'none' } }, errorText)),
373
+ h("div", { id: helperTextId, class: "helper-text" }, helperText),
374
+ h("div", { id: errorTextId, class: "error-text" }, errorText),
429
375
  ];
430
376
  }
431
377
  getHintTextID() {
432
- const { isInvalid, helperText, errorText, helperTextId, errorTextId } = this;
433
- if (isInvalid && errorText) {
378
+ const { el, helperText, errorText, helperTextId, errorTextId } = this;
379
+ if (el.classList.contains('ion-touched') && el.classList.contains('ion-invalid') && errorText) {
434
380
  return errorTextId;
435
381
  }
436
382
  if (helperText) {
@@ -543,7 +489,7 @@ const Input = /*@__PURE__*/ proxyCustomElement(class Input extends HTMLElement {
543
489
  * TODO(FW-5592): Remove hasStartEndSlots condition
544
490
  */
545
491
  const labelShouldFloat = labelPlacement === 'stacked' || (labelPlacement === 'floating' && (hasValue || hasFocus || hasStartEndSlots));
546
- return (h(Host, { key: '1a141906096fc637d7de923edf6a9ea3a0168f5f', class: createColorClasses(this.color, {
492
+ return (h(Host, { key: '41b2526627e7d2773a80f011b123284203a71ca0', class: createColorClasses(this.color, {
547
493
  [mode]: true,
548
494
  'has-value': hasValue,
549
495
  'has-focus': hasFocus,
@@ -554,14 +500,14 @@ const Input = /*@__PURE__*/ proxyCustomElement(class Input extends HTMLElement {
554
500
  'in-item': inItem,
555
501
  'in-item-color': hostContext('ion-item.ion-color', this.el),
556
502
  'input-disabled': disabled,
557
- }) }, h("label", { key: '8371432bc0fd37099d3efc50705530965f27b9e1', class: "input-wrapper", htmlFor: inputId, onClick: this.onLabelClick }, this.renderLabelContainer(), h("div", { key: '3d113da062bddf47b1bc67e67162c11fe4ee7f10', class: "native-wrapper", onClick: this.onLabelClick }, h("slot", { key: '3fe52ada4b91ebd21b21b1445b9d4009d47f8386', name: "start" }), h("input", Object.assign({ key: 'beda905e9d44797690149407ba814381ffd51d1d', class: "native-input", ref: (input) => (this.nativeInput = input), id: inputId, disabled: disabled, autoCapitalize: this.autocapitalize, autoComplete: this.autocomplete, autoCorrect: this.autocorrect, autoFocus: this.autofocus, enterKeyHint: this.enterkeyhint, inputMode: this.inputmode, min: this.min, max: this.max, minLength: this.minlength, maxLength: this.maxlength, multiple: this.multiple, name: this.name, pattern: this.pattern, placeholder: this.placeholder || '', readOnly: readonly, required: this.required, spellcheck: this.spellcheck, step: this.step, type: this.type, value: value, onInput: this.onInput, onChange: this.onChange, onBlur: this.onBlur, onFocus: this.onFocus, onKeyDown: this.onKeydown, onCompositionstart: this.onCompositionStart, onCompositionend: this.onCompositionEnd, "aria-describedby": this.getHintTextID(), "aria-invalid": this.isInvalid ? 'true' : undefined }, this.inheritedAttributes)), this.clearInput && !readonly && !disabled && (h("button", { key: 'bc8579a0acb727b348bcf277f296b2dac9cb583d', "aria-label": "reset", type: "button", class: "input-clear-icon", onPointerDown: (ev) => {
503
+ }) }, h("label", { key: '9ab078363e32528102b441ad1791d83f86fdcbdc', class: "input-wrapper", htmlFor: inputId, onClick: this.onLabelClick }, this.renderLabelContainer(), h("div", { key: 'e34b594980ec62e4c618e827fadf7669a39ad0d8', class: "native-wrapper", onClick: this.onLabelClick }, h("slot", { key: '12dc04ead5502e9e5736240e918bf9331bf7b5d9', name: "start" }), h("input", Object.assign({ key: 'df356eb4ced23109b2c0242f36dc043aba8782d6', class: "native-input", ref: (input) => (this.nativeInput = input), id: inputId, disabled: disabled, autoCapitalize: this.autocapitalize, autoComplete: this.autocomplete, autoCorrect: this.autocorrect, autoFocus: this.autofocus, enterKeyHint: this.enterkeyhint, inputMode: this.inputmode, min: this.min, max: this.max, minLength: this.minlength, maxLength: this.maxlength, multiple: this.multiple, name: this.name, pattern: this.pattern, placeholder: this.placeholder || '', readOnly: readonly, required: this.required, spellcheck: this.spellcheck, step: this.step, type: this.type, value: value, onInput: this.onInput, onChange: this.onChange, onBlur: this.onBlur, onFocus: this.onFocus, onKeyDown: this.onKeydown, onCompositionstart: this.onCompositionStart, onCompositionend: this.onCompositionEnd, "aria-describedby": this.getHintTextID(), "aria-invalid": this.getHintTextID() === this.errorTextId }, this.inheritedAttributes)), this.clearInput && !readonly && !disabled && (h("button", { key: 'f79f68cabcd4ea99419331174a377827db0c0741', "aria-label": "reset", type: "button", class: "input-clear-icon", onPointerDown: (ev) => {
558
504
  /**
559
505
  * This prevents mobile browsers from
560
506
  * blurring the input when the clear
561
507
  * button is activated.
562
508
  */
563
509
  ev.preventDefault();
564
- }, onClick: this.clearTextInput }, h("ion-icon", { key: '41a64ec6fce010eb225637998d93abd859a62be7', "aria-hidden": "true", icon: clearIconData }))), h("slot", { key: '9a7e6eba584748fc037aa1694d7e6e399de8b20b', name: "end" })), shouldRenderHighlight && h("div", { key: 'b828e6dc43b50f46727dda196de7ad2fecc3ef30', class: "input-highlight" })), this.renderBottomContent()));
510
+ }, onClick: this.clearTextInput }, h("ion-icon", { key: '237ec07ec2e10f08818a332bb596578c2c49f770', "aria-hidden": "true", icon: clearIconData }))), h("slot", { key: '1f0a3624aa3e8dc3c307a6762230ab698768a5e5', name: "end" })), shouldRenderHighlight && h("div", { key: '8a8cbb82695a722a0010b53dd0b1f1f97534a20b', class: "input-highlight" })), this.renderBottomContent()));
565
511
  }
566
512
  get el() { return this; }
567
513
  static get watchers() { return {
@@ -610,7 +556,6 @@ const Input = /*@__PURE__*/ proxyCustomElement(class Input extends HTMLElement {
610
556
  "type": [1],
611
557
  "value": [1032],
612
558
  "hasFocus": [32],
613
- "isInvalid": [32],
614
559
  "setFocus": [64],
615
560
  "getInputElement": [64]
616
561
  }, [[2, "click", "onClickCapture"]], {
@@ -40,10 +40,6 @@ const Textarea = /*@__PURE__*/ proxyCustomElement(class Textarea extends HTMLEle
40
40
  * is applied in both cases.
41
41
  */
42
42
  this.hasFocus = false;
43
- /**
44
- * Track validation state for proper aria-live announcements
45
- */
46
- this.isInvalid = false;
47
43
  /**
48
44
  * Indicates whether and how the text value should be automatically capitalized as it is entered/edited by the user.
49
45
  * Available options: `"off"`, `"none"`, `"on"`, `"sentences"`, `"words"`, `"characters"`.
@@ -133,19 +129,6 @@ const Textarea = /*@__PURE__*/ proxyCustomElement(class Textarea extends HTMLEle
133
129
  }
134
130
  this.didTextareaClearOnEdit = false;
135
131
  this.ionBlur.emit(ev);
136
- /**
137
- * Check validation state after blur to handle framework-managed classes.
138
- * Frameworks like Angular update classes asynchronously, often using
139
- * requestAnimationFrame or promises. Using setTimeout ensures we check
140
- * after all microtasks and animation frames have completed.
141
- */
142
- setTimeout(() => {
143
- const newIsInvalid = this.checkValidationState();
144
- if (this.isInvalid !== newIsInvalid) {
145
- this.isInvalid = newIsInvalid;
146
- forceUpdate(this);
147
- }
148
- }, 100);
149
132
  };
150
133
  this.onKeyDown = (ev) => {
151
134
  this.checkClearOnEdit(ev);
@@ -203,42 +186,10 @@ const Textarea = /*@__PURE__*/ proxyCustomElement(class Textarea extends HTMLEle
203
186
  this.el.click();
204
187
  }
205
188
  }
206
- /**
207
- * Checks if the textarea is in an invalid state based on validation classes
208
- */
209
- checkValidationState() {
210
- // Check for both Ionic and Angular validation classes on the element itself
211
- // Angular applies ng-touched/ng-invalid directly to the host element with ngModel
212
- const hasIonTouched = this.el.classList.contains('ion-touched');
213
- const hasIonInvalid = this.el.classList.contains('ion-invalid');
214
- const hasNgTouched = this.el.classList.contains('ng-touched');
215
- const hasNgInvalid = this.el.classList.contains('ng-invalid');
216
- // Return true if we have both touched and invalid states from either framework
217
- const isTouched = hasIonTouched || hasNgTouched;
218
- const isInvalid = hasIonInvalid || hasNgInvalid;
219
- return isTouched && isInvalid;
220
- }
221
189
  connectedCallback() {
222
190
  const { el } = this;
223
191
  this.slotMutationController = createSlotMutationController(el, ['label', 'start', 'end'], () => forceUpdate(this));
224
192
  this.notchController = createNotchController(el, () => this.notchSpacerEl, () => this.labelSlot);
225
- // Watch for class changes to update validation state
226
- if (Build.isBrowser && typeof MutationObserver !== 'undefined') {
227
- this.validationObserver = new MutationObserver(() => {
228
- const newIsInvalid = this.checkValidationState();
229
- if (this.isInvalid !== newIsInvalid) {
230
- this.isInvalid = newIsInvalid;
231
- // Force a re-render to update aria-describedby immediately
232
- forceUpdate(this);
233
- }
234
- });
235
- this.validationObserver.observe(el, {
236
- attributes: true,
237
- attributeFilter: ['class'],
238
- });
239
- }
240
- // Always set initial state
241
- this.isInvalid = this.checkValidationState();
242
193
  this.debounceChanged();
243
194
  if (Build.isBrowser) {
244
195
  document.dispatchEvent(new CustomEvent('ionInputDidLoad', {
@@ -260,11 +211,6 @@ const Textarea = /*@__PURE__*/ proxyCustomElement(class Textarea extends HTMLEle
260
211
  this.notchController.destroy();
261
212
  this.notchController = undefined;
262
213
  }
263
- // Clean up validation observer to prevent memory leaks
264
- if (this.validationObserver) {
265
- this.validationObserver.disconnect();
266
- this.validationObserver = undefined;
267
- }
268
214
  }
269
215
  componentWillLoad() {
270
216
  this.inheritedAttributes = Object.assign(Object.assign({}, inheritAriaAttributes(this.el)), inheritAttributes(this.el, ['data-form-type', 'title', 'tabindex', 'dir']));
@@ -435,15 +381,15 @@ const Textarea = /*@__PURE__*/ proxyCustomElement(class Textarea extends HTMLEle
435
381
  * Renders the helper text or error text values
436
382
  */
437
383
  renderHintText() {
438
- const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
384
+ const { helperText, errorText, helperTextId, errorTextId } = this;
439
385
  return [
440
- helperText && (h("div", { id: helperTextId, class: "helper-text", "aria-live": "polite" }, helperText)),
441
- errorText && (h("div", { id: errorTextId, class: "error-text", "aria-live": "assertive", "aria-atomic": "true", role: "alert", style: { display: isInvalid ? 'block' : 'none' } }, errorText)),
386
+ h("div", { id: helperTextId, class: "helper-text" }, helperText),
387
+ h("div", { id: errorTextId, class: "error-text" }, errorText),
442
388
  ];
443
389
  }
444
390
  getHintTextID() {
445
- const { isInvalid, helperText, errorText, helperTextId, errorTextId } = this;
446
- if (isInvalid && errorText) {
391
+ const { el, helperText, errorText, helperTextId, errorTextId } = this;
392
+ if (el.classList.contains('ion-touched') && el.classList.contains('ion-invalid') && errorText) {
447
393
  return errorTextId;
448
394
  }
449
395
  if (helperText) {
@@ -502,7 +448,7 @@ const Textarea = /*@__PURE__*/ proxyCustomElement(class Textarea extends HTMLEle
502
448
  * TODO(FW-5592): Remove hasStartEndSlots condition
503
449
  */
504
450
  const labelShouldFloat = labelPlacement === 'stacked' || (labelPlacement === 'floating' && (hasValue || hasFocus || hasStartEndSlots));
505
- return (h(Host, { key: '803c7648de15b8569c3df01692548018cc660510', class: createColorClasses(this.color, {
451
+ return (h(Host, { key: 'd9f2ede0107987fc42c99e310cd2336bad5a5755', class: createColorClasses(this.color, {
506
452
  [mode]: true,
507
453
  'has-value': hasValue,
508
454
  'has-focus': hasFocus,
@@ -511,7 +457,7 @@ const Textarea = /*@__PURE__*/ proxyCustomElement(class Textarea extends HTMLEle
511
457
  [`textarea-shape-${shape}`]: shape !== undefined,
512
458
  [`textarea-label-placement-${labelPlacement}`]: true,
513
459
  'textarea-disabled': disabled,
514
- }) }, h("label", { key: '0e3a4e5fc809437abc6780eb7f99a0c770bb9f94', class: "textarea-wrapper", htmlFor: inputId, onClick: this.onLabelClick }, this.renderLabelContainer(), h("div", { key: 'ff8d71e2d53daf4999338232d3b0a597627a3276', class: "textarea-wrapper-inner" }, h("div", { key: '313f5a8dc8b62e8ae6a0638a7e201b2594c180eb', class: "start-slot-wrapper" }, h("slot", { key: '32db088d41398be935f7060138e4d80a832fea85', name: "start" })), h("div", { key: '24b63ff40ffcae8816f24d9c8a6ec2e7848e0e0b', class: "native-wrapper", ref: (el) => (this.textareaWrapper = el) }, h("textarea", Object.assign({ key: '2a4f918351f87d444d8922254eefa0941a6ca238', class: "native-textarea", ref: (el) => (this.nativeInput = el), id: inputId, disabled: disabled, autoCapitalize: this.autocapitalize, autoFocus: this.autofocus, enterKeyHint: this.enterkeyhint, inputMode: this.inputmode, minLength: this.minlength, maxLength: this.maxlength, name: this.name, placeholder: this.placeholder || '', readOnly: this.readonly, required: this.required, spellcheck: this.spellcheck, cols: this.cols, rows: this.rows, wrap: this.wrap, onInput: this.onInput, onChange: this.onChange, onBlur: this.onBlur, onFocus: this.onFocus, onKeyDown: this.onKeyDown, "aria-describedby": this.getHintTextID(), "aria-invalid": this.isInvalid ? 'true' : undefined }, this.inheritedAttributes), value)), h("div", { key: '2c32636d2d13560de0a54507e1668eb9f4bcc2cf', class: "end-slot-wrapper" }, h("slot", { key: 'ccc3549246fd9d2e309958767a7a24e1be8545fd', name: "end" }))), shouldRenderHighlight && h("div", { key: 'f4719399ec530ce295b19817688b8d39d84b6b09', class: "textarea-highlight" })), this.renderBottomContent()));
460
+ }) }, h("label", { key: '9de598b95237462bb3bccffaefe83afbb43554b8', class: "textarea-wrapper", htmlFor: inputId, onClick: this.onLabelClick }, this.renderLabelContainer(), h("div", { key: 'e33c426c6541d723ccc246bb404c03687726ff83', class: "textarea-wrapper-inner" }, h("div", { key: '521e11af9d54d281b0a2b1c25bcfc6f742c18296', class: "start-slot-wrapper" }, h("slot", { key: '515523f6ca3ce0e5dd08f3275c21a190fb1ca177', name: "start" })), h("div", { key: '916e01e00de8400ae00ef06bc1fb62d8be2eee08', class: "native-wrapper", ref: (el) => (this.textareaWrapper = el) }, h("textarea", Object.assign({ key: '810271e6532d90e27dab1fcb26546113c1ce9cb0', class: "native-textarea", ref: (el) => (this.nativeInput = el), id: inputId, disabled: disabled, autoCapitalize: this.autocapitalize, autoFocus: this.autofocus, enterKeyHint: this.enterkeyhint, inputMode: this.inputmode, minLength: this.minlength, maxLength: this.maxlength, name: this.name, placeholder: this.placeholder || '', readOnly: this.readonly, required: this.required, spellcheck: this.spellcheck, cols: this.cols, rows: this.rows, wrap: this.wrap, onInput: this.onInput, onChange: this.onChange, onBlur: this.onBlur, onFocus: this.onFocus, onKeyDown: this.onKeyDown, "aria-describedby": this.getHintTextID(), "aria-invalid": this.getHintTextID() === this.errorTextId }, this.inheritedAttributes), value)), h("div", { key: '80aca9ea9546dca9d38efd291a6b0be384bb6978', class: "end-slot-wrapper" }, h("slot", { key: '407fab16c66a9f4a542369bfecc0d9afa0065977', name: "end" }))), shouldRenderHighlight && h("div", { key: 'f00523a6698fac8a1996e04303487bef01d10f25', class: "textarea-highlight" })), this.renderBottomContent()));
515
461
  }
516
462
  get el() { return this; }
517
463
  static get watchers() { return {
@@ -553,7 +499,6 @@ const Textarea = /*@__PURE__*/ proxyCustomElement(class Textarea extends HTMLEle
553
499
  "labelPlacement": [1, "label-placement"],
554
500
  "shape": [1],
555
501
  "hasFocus": [32],
556
- "isInvalid": [32],
557
502
  "setFocus": [64],
558
503
  "getInputElement": [64]
559
504
  }, [[2, "click", "onClickCapture"]], {
@@ -44,10 +44,6 @@ const Input = class {
44
44
  * is applied in both cases.
45
45
  */
46
46
  this.hasFocus = false;
47
- /**
48
- * Track validation state for proper aria-live announcements
49
- */
50
- this.isInvalid = false;
51
47
  /**
52
48
  * Indicates whether and how the text value should be automatically capitalized as it is entered/edited by the user.
53
49
  * Available options: `"off"`, `"none"`, `"on"`, `"sentences"`, `"words"`, `"characters"`.
@@ -133,19 +129,6 @@ const Input = class {
133
129
  }
134
130
  this.didInputClearOnEdit = false;
135
131
  this.ionBlur.emit(ev);
136
- /**
137
- * Check validation state after blur to handle framework-managed classes.
138
- * Frameworks like Angular update classes asynchronously, often using
139
- * requestAnimationFrame or promises. Using setTimeout ensures we check
140
- * after all microtasks and animation frames have completed.
141
- */
142
- setTimeout(() => {
143
- const newIsInvalid = this.checkValidationState();
144
- if (this.isInvalid !== newIsInvalid) {
145
- this.isInvalid = newIsInvalid;
146
- index.forceUpdate(this);
147
- }
148
- }, 100);
149
132
  };
150
133
  this.onFocus = (ev) => {
151
134
  this.hasFocus = true;
@@ -245,42 +228,10 @@ const Input = class {
245
228
  componentWillLoad() {
246
229
  this.inheritedAttributes = Object.assign(Object.assign({}, helpers.inheritAriaAttributes(this.el)), helpers.inheritAttributes(this.el, ['tabindex', 'title', 'data-form-type', 'dir']));
247
230
  }
248
- /**
249
- * Checks if the input is in an invalid state based on validation classes
250
- */
251
- checkValidationState() {
252
- // Check for both Ionic and Angular validation classes on the element itself
253
- // Angular applies ng-touched/ng-invalid directly to the host element with ngModel
254
- const hasIonTouched = this.el.classList.contains('ion-touched');
255
- const hasIonInvalid = this.el.classList.contains('ion-invalid');
256
- const hasNgTouched = this.el.classList.contains('ng-touched');
257
- const hasNgInvalid = this.el.classList.contains('ng-invalid');
258
- // Return true if we have both touched and invalid states from either framework
259
- const isTouched = hasIonTouched || hasNgTouched;
260
- const isInvalid = hasIonInvalid || hasNgInvalid;
261
- return isTouched && isInvalid;
262
- }
263
231
  connectedCallback() {
264
232
  const { el } = this;
265
233
  this.slotMutationController = input_utils.createSlotMutationController(el, ['label', 'start', 'end'], () => index.forceUpdate(this));
266
234
  this.notchController = notchController.createNotchController(el, () => this.notchSpacerEl, () => this.labelSlot);
267
- // Watch for class changes to update validation state
268
- if (typeof MutationObserver !== 'undefined') {
269
- this.validationObserver = new MutationObserver(() => {
270
- const newIsInvalid = this.checkValidationState();
271
- if (this.isInvalid !== newIsInvalid) {
272
- this.isInvalid = newIsInvalid;
273
- // Force a re-render to update aria-describedby immediately
274
- index.forceUpdate(this);
275
- }
276
- });
277
- this.validationObserver.observe(el, {
278
- attributes: true,
279
- attributeFilter: ['class'],
280
- });
281
- }
282
- // Always set initial state
283
- this.isInvalid = this.checkValidationState();
284
235
  this.debounceChanged();
285
236
  {
286
237
  document.dispatchEvent(new CustomEvent('ionInputDidLoad', {
@@ -317,11 +268,6 @@ const Input = class {
317
268
  this.notchController.destroy();
318
269
  this.notchController = undefined;
319
270
  }
320
- // Clean up validation observer to prevent memory leaks
321
- if (this.validationObserver) {
322
- this.validationObserver.disconnect();
323
- this.validationObserver = undefined;
324
- }
325
271
  }
326
272
  /**
327
273
  * Sets focus on the native `input` in `ion-input`. Use this method instead of the global
@@ -423,15 +369,15 @@ const Input = class {
423
369
  * Renders the helper text or error text values
424
370
  */
425
371
  renderHintText() {
426
- const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
372
+ const { helperText, errorText, helperTextId, errorTextId } = this;
427
373
  return [
428
- helperText && (index.h("div", { id: helperTextId, class: "helper-text", "aria-live": "polite" }, helperText)),
429
- errorText && (index.h("div", { id: errorTextId, class: "error-text", "aria-live": "assertive", "aria-atomic": "true", role: "alert", style: { display: isInvalid ? 'block' : 'none' } }, errorText)),
374
+ index.h("div", { id: helperTextId, class: "helper-text" }, helperText),
375
+ index.h("div", { id: errorTextId, class: "error-text" }, errorText),
430
376
  ];
431
377
  }
432
378
  getHintTextID() {
433
- const { isInvalid, helperText, errorText, helperTextId, errorTextId } = this;
434
- if (isInvalid && errorText) {
379
+ const { el, helperText, errorText, helperTextId, errorTextId } = this;
380
+ if (el.classList.contains('ion-touched') && el.classList.contains('ion-invalid') && errorText) {
435
381
  return errorTextId;
436
382
  }
437
383
  if (helperText) {
@@ -544,7 +490,7 @@ const Input = class {
544
490
  * TODO(FW-5592): Remove hasStartEndSlots condition
545
491
  */
546
492
  const labelShouldFloat = labelPlacement === 'stacked' || (labelPlacement === 'floating' && (hasValue || hasFocus || hasStartEndSlots));
547
- return (index.h(index.Host, { key: '1a141906096fc637d7de923edf6a9ea3a0168f5f', class: theme.createColorClasses(this.color, {
493
+ return (index.h(index.Host, { key: '41b2526627e7d2773a80f011b123284203a71ca0', class: theme.createColorClasses(this.color, {
548
494
  [mode]: true,
549
495
  'has-value': hasValue,
550
496
  'has-focus': hasFocus,
@@ -555,14 +501,14 @@ const Input = class {
555
501
  'in-item': inItem,
556
502
  'in-item-color': theme.hostContext('ion-item.ion-color', this.el),
557
503
  'input-disabled': disabled,
558
- }) }, index.h("label", { key: '8371432bc0fd37099d3efc50705530965f27b9e1', class: "input-wrapper", htmlFor: inputId, onClick: this.onLabelClick }, this.renderLabelContainer(), index.h("div", { key: '3d113da062bddf47b1bc67e67162c11fe4ee7f10', class: "native-wrapper", onClick: this.onLabelClick }, index.h("slot", { key: '3fe52ada4b91ebd21b21b1445b9d4009d47f8386', name: "start" }), index.h("input", Object.assign({ key: 'beda905e9d44797690149407ba814381ffd51d1d', class: "native-input", ref: (input) => (this.nativeInput = input), id: inputId, disabled: disabled, autoCapitalize: this.autocapitalize, autoComplete: this.autocomplete, autoCorrect: this.autocorrect, autoFocus: this.autofocus, enterKeyHint: this.enterkeyhint, inputMode: this.inputmode, min: this.min, max: this.max, minLength: this.minlength, maxLength: this.maxlength, multiple: this.multiple, name: this.name, pattern: this.pattern, placeholder: this.placeholder || '', readOnly: readonly, required: this.required, spellcheck: this.spellcheck, step: this.step, type: this.type, value: value, onInput: this.onInput, onChange: this.onChange, onBlur: this.onBlur, onFocus: this.onFocus, onKeyDown: this.onKeydown, onCompositionstart: this.onCompositionStart, onCompositionend: this.onCompositionEnd, "aria-describedby": this.getHintTextID(), "aria-invalid": this.isInvalid ? 'true' : undefined }, this.inheritedAttributes)), this.clearInput && !readonly && !disabled && (index.h("button", { key: 'bc8579a0acb727b348bcf277f296b2dac9cb583d', "aria-label": "reset", type: "button", class: "input-clear-icon", onPointerDown: (ev) => {
504
+ }) }, index.h("label", { key: '9ab078363e32528102b441ad1791d83f86fdcbdc', class: "input-wrapper", htmlFor: inputId, onClick: this.onLabelClick }, this.renderLabelContainer(), index.h("div", { key: 'e34b594980ec62e4c618e827fadf7669a39ad0d8', class: "native-wrapper", onClick: this.onLabelClick }, index.h("slot", { key: '12dc04ead5502e9e5736240e918bf9331bf7b5d9', name: "start" }), index.h("input", Object.assign({ key: 'df356eb4ced23109b2c0242f36dc043aba8782d6', class: "native-input", ref: (input) => (this.nativeInput = input), id: inputId, disabled: disabled, autoCapitalize: this.autocapitalize, autoComplete: this.autocomplete, autoCorrect: this.autocorrect, autoFocus: this.autofocus, enterKeyHint: this.enterkeyhint, inputMode: this.inputmode, min: this.min, max: this.max, minLength: this.minlength, maxLength: this.maxlength, multiple: this.multiple, name: this.name, pattern: this.pattern, placeholder: this.placeholder || '', readOnly: readonly, required: this.required, spellcheck: this.spellcheck, step: this.step, type: this.type, value: value, onInput: this.onInput, onChange: this.onChange, onBlur: this.onBlur, onFocus: this.onFocus, onKeyDown: this.onKeydown, onCompositionstart: this.onCompositionStart, onCompositionend: this.onCompositionEnd, "aria-describedby": this.getHintTextID(), "aria-invalid": this.getHintTextID() === this.errorTextId }, this.inheritedAttributes)), this.clearInput && !readonly && !disabled && (index.h("button", { key: 'f79f68cabcd4ea99419331174a377827db0c0741', "aria-label": "reset", type: "button", class: "input-clear-icon", onPointerDown: (ev) => {
559
505
  /**
560
506
  * This prevents mobile browsers from
561
507
  * blurring the input when the clear
562
508
  * button is activated.
563
509
  */
564
510
  ev.preventDefault();
565
- }, onClick: this.clearTextInput }, index.h("ion-icon", { key: '41a64ec6fce010eb225637998d93abd859a62be7', "aria-hidden": "true", icon: clearIconData }))), index.h("slot", { key: '9a7e6eba584748fc037aa1694d7e6e399de8b20b', name: "end" })), shouldRenderHighlight && index.h("div", { key: 'b828e6dc43b50f46727dda196de7ad2fecc3ef30', class: "input-highlight" })), this.renderBottomContent()));
511
+ }, onClick: this.clearTextInput }, index.h("ion-icon", { key: '237ec07ec2e10f08818a332bb596578c2c49f770', "aria-hidden": "true", icon: clearIconData }))), index.h("slot", { key: '1f0a3624aa3e8dc3c307a6762230ab698768a5e5', name: "end" })), shouldRenderHighlight && index.h("div", { key: '8a8cbb82695a722a0010b53dd0b1f1f97534a20b', class: "input-highlight" })), this.renderBottomContent()));
566
512
  }
567
513
  get el() { return index.getElement(this); }
568
514
  static get watchers() { return {
@@ -42,10 +42,6 @@ const Textarea = class {
42
42
  * is applied in both cases.
43
43
  */
44
44
  this.hasFocus = false;
45
- /**
46
- * Track validation state for proper aria-live announcements
47
- */
48
- this.isInvalid = false;
49
45
  /**
50
46
  * Indicates whether and how the text value should be automatically capitalized as it is entered/edited by the user.
51
47
  * Available options: `"off"`, `"none"`, `"on"`, `"sentences"`, `"words"`, `"characters"`.
@@ -135,19 +131,6 @@ const Textarea = class {
135
131
  }
136
132
  this.didTextareaClearOnEdit = false;
137
133
  this.ionBlur.emit(ev);
138
- /**
139
- * Check validation state after blur to handle framework-managed classes.
140
- * Frameworks like Angular update classes asynchronously, often using
141
- * requestAnimationFrame or promises. Using setTimeout ensures we check
142
- * after all microtasks and animation frames have completed.
143
- */
144
- setTimeout(() => {
145
- const newIsInvalid = this.checkValidationState();
146
- if (this.isInvalid !== newIsInvalid) {
147
- this.isInvalid = newIsInvalid;
148
- index.forceUpdate(this);
149
- }
150
- }, 100);
151
134
  };
152
135
  this.onKeyDown = (ev) => {
153
136
  this.checkClearOnEdit(ev);
@@ -205,42 +188,10 @@ const Textarea = class {
205
188
  this.el.click();
206
189
  }
207
190
  }
208
- /**
209
- * Checks if the textarea is in an invalid state based on validation classes
210
- */
211
- checkValidationState() {
212
- // Check for both Ionic and Angular validation classes on the element itself
213
- // Angular applies ng-touched/ng-invalid directly to the host element with ngModel
214
- const hasIonTouched = this.el.classList.contains('ion-touched');
215
- const hasIonInvalid = this.el.classList.contains('ion-invalid');
216
- const hasNgTouched = this.el.classList.contains('ng-touched');
217
- const hasNgInvalid = this.el.classList.contains('ng-invalid');
218
- // Return true if we have both touched and invalid states from either framework
219
- const isTouched = hasIonTouched || hasNgTouched;
220
- const isInvalid = hasIonInvalid || hasNgInvalid;
221
- return isTouched && isInvalid;
222
- }
223
191
  connectedCallback() {
224
192
  const { el } = this;
225
193
  this.slotMutationController = input_utils.createSlotMutationController(el, ['label', 'start', 'end'], () => index.forceUpdate(this));
226
194
  this.notchController = notchController.createNotchController(el, () => this.notchSpacerEl, () => this.labelSlot);
227
- // Watch for class changes to update validation state
228
- if (typeof MutationObserver !== 'undefined') {
229
- this.validationObserver = new MutationObserver(() => {
230
- const newIsInvalid = this.checkValidationState();
231
- if (this.isInvalid !== newIsInvalid) {
232
- this.isInvalid = newIsInvalid;
233
- // Force a re-render to update aria-describedby immediately
234
- index.forceUpdate(this);
235
- }
236
- });
237
- this.validationObserver.observe(el, {
238
- attributes: true,
239
- attributeFilter: ['class'],
240
- });
241
- }
242
- // Always set initial state
243
- this.isInvalid = this.checkValidationState();
244
195
  this.debounceChanged();
245
196
  {
246
197
  document.dispatchEvent(new CustomEvent('ionInputDidLoad', {
@@ -262,11 +213,6 @@ const Textarea = class {
262
213
  this.notchController.destroy();
263
214
  this.notchController = undefined;
264
215
  }
265
- // Clean up validation observer to prevent memory leaks
266
- if (this.validationObserver) {
267
- this.validationObserver.disconnect();
268
- this.validationObserver = undefined;
269
- }
270
216
  }
271
217
  componentWillLoad() {
272
218
  this.inheritedAttributes = Object.assign(Object.assign({}, helpers.inheritAriaAttributes(this.el)), helpers.inheritAttributes(this.el, ['data-form-type', 'title', 'tabindex', 'dir']));
@@ -437,15 +383,15 @@ const Textarea = class {
437
383
  * Renders the helper text or error text values
438
384
  */
439
385
  renderHintText() {
440
- const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
386
+ const { helperText, errorText, helperTextId, errorTextId } = this;
441
387
  return [
442
- helperText && (index.h("div", { id: helperTextId, class: "helper-text", "aria-live": "polite" }, helperText)),
443
- errorText && (index.h("div", { id: errorTextId, class: "error-text", "aria-live": "assertive", "aria-atomic": "true", role: "alert", style: { display: isInvalid ? 'block' : 'none' } }, errorText)),
388
+ index.h("div", { id: helperTextId, class: "helper-text" }, helperText),
389
+ index.h("div", { id: errorTextId, class: "error-text" }, errorText),
444
390
  ];
445
391
  }
446
392
  getHintTextID() {
447
- const { isInvalid, helperText, errorText, helperTextId, errorTextId } = this;
448
- if (isInvalid && errorText) {
393
+ const { el, helperText, errorText, helperTextId, errorTextId } = this;
394
+ if (el.classList.contains('ion-touched') && el.classList.contains('ion-invalid') && errorText) {
449
395
  return errorTextId;
450
396
  }
451
397
  if (helperText) {
@@ -504,7 +450,7 @@ const Textarea = class {
504
450
  * TODO(FW-5592): Remove hasStartEndSlots condition
505
451
  */
506
452
  const labelShouldFloat = labelPlacement === 'stacked' || (labelPlacement === 'floating' && (hasValue || hasFocus || hasStartEndSlots));
507
- return (index.h(index.Host, { key: '803c7648de15b8569c3df01692548018cc660510', class: theme.createColorClasses(this.color, {
453
+ return (index.h(index.Host, { key: 'd9f2ede0107987fc42c99e310cd2336bad5a5755', class: theme.createColorClasses(this.color, {
508
454
  [mode]: true,
509
455
  'has-value': hasValue,
510
456
  'has-focus': hasFocus,
@@ -513,7 +459,7 @@ const Textarea = class {
513
459
  [`textarea-shape-${shape}`]: shape !== undefined,
514
460
  [`textarea-label-placement-${labelPlacement}`]: true,
515
461
  'textarea-disabled': disabled,
516
- }) }, index.h("label", { key: '0e3a4e5fc809437abc6780eb7f99a0c770bb9f94', class: "textarea-wrapper", htmlFor: inputId, onClick: this.onLabelClick }, this.renderLabelContainer(), index.h("div", { key: 'ff8d71e2d53daf4999338232d3b0a597627a3276', class: "textarea-wrapper-inner" }, index.h("div", { key: '313f5a8dc8b62e8ae6a0638a7e201b2594c180eb', class: "start-slot-wrapper" }, index.h("slot", { key: '32db088d41398be935f7060138e4d80a832fea85', name: "start" })), index.h("div", { key: '24b63ff40ffcae8816f24d9c8a6ec2e7848e0e0b', class: "native-wrapper", ref: (el) => (this.textareaWrapper = el) }, index.h("textarea", Object.assign({ key: '2a4f918351f87d444d8922254eefa0941a6ca238', class: "native-textarea", ref: (el) => (this.nativeInput = el), id: inputId, disabled: disabled, autoCapitalize: this.autocapitalize, autoFocus: this.autofocus, enterKeyHint: this.enterkeyhint, inputMode: this.inputmode, minLength: this.minlength, maxLength: this.maxlength, name: this.name, placeholder: this.placeholder || '', readOnly: this.readonly, required: this.required, spellcheck: this.spellcheck, cols: this.cols, rows: this.rows, wrap: this.wrap, onInput: this.onInput, onChange: this.onChange, onBlur: this.onBlur, onFocus: this.onFocus, onKeyDown: this.onKeyDown, "aria-describedby": this.getHintTextID(), "aria-invalid": this.isInvalid ? 'true' : undefined }, this.inheritedAttributes), value)), index.h("div", { key: '2c32636d2d13560de0a54507e1668eb9f4bcc2cf', class: "end-slot-wrapper" }, index.h("slot", { key: 'ccc3549246fd9d2e309958767a7a24e1be8545fd', name: "end" }))), shouldRenderHighlight && index.h("div", { key: 'f4719399ec530ce295b19817688b8d39d84b6b09', class: "textarea-highlight" })), this.renderBottomContent()));
462
+ }) }, index.h("label", { key: '9de598b95237462bb3bccffaefe83afbb43554b8', class: "textarea-wrapper", htmlFor: inputId, onClick: this.onLabelClick }, this.renderLabelContainer(), index.h("div", { key: 'e33c426c6541d723ccc246bb404c03687726ff83', class: "textarea-wrapper-inner" }, index.h("div", { key: '521e11af9d54d281b0a2b1c25bcfc6f742c18296', class: "start-slot-wrapper" }, index.h("slot", { key: '515523f6ca3ce0e5dd08f3275c21a190fb1ca177', name: "start" })), index.h("div", { key: '916e01e00de8400ae00ef06bc1fb62d8be2eee08', class: "native-wrapper", ref: (el) => (this.textareaWrapper = el) }, index.h("textarea", Object.assign({ key: '810271e6532d90e27dab1fcb26546113c1ce9cb0', class: "native-textarea", ref: (el) => (this.nativeInput = el), id: inputId, disabled: disabled, autoCapitalize: this.autocapitalize, autoFocus: this.autofocus, enterKeyHint: this.enterkeyhint, inputMode: this.inputmode, minLength: this.minlength, maxLength: this.maxlength, name: this.name, placeholder: this.placeholder || '', readOnly: this.readonly, required: this.required, spellcheck: this.spellcheck, cols: this.cols, rows: this.rows, wrap: this.wrap, onInput: this.onInput, onChange: this.onChange, onBlur: this.onBlur, onFocus: this.onFocus, onKeyDown: this.onKeyDown, "aria-describedby": this.getHintTextID(), "aria-invalid": this.getHintTextID() === this.errorTextId }, this.inheritedAttributes), value)), index.h("div", { key: '80aca9ea9546dca9d38efd291a6b0be384bb6978', class: "end-slot-wrapper" }, index.h("slot", { key: '407fab16c66a9f4a542369bfecc0d9afa0065977', name: "end" }))), shouldRenderHighlight && index.h("div", { key: 'f00523a6698fac8a1996e04303487bef01d10f25', class: "textarea-highlight" })), this.renderBottomContent()));
517
463
  }
518
464
  get el() { return index.getElement(this); }
519
465
  static get watchers() { return {