@ionic/core 8.7.7-nightly.20251015 → 8.7.7

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.
Files changed (52) hide show
  1. package/components/button.js +3 -7
  2. package/components/header.js +41 -3
  3. package/components/ion-input.js +6 -14
  4. package/components/ion-select.js +58 -10
  5. package/components/ion-textarea.js +5 -13
  6. package/components/{notch-controller.js → validity.js} +14 -1
  7. package/dist/cjs/ion-app_8.cjs.entry.js +41 -3
  8. package/dist/cjs/ion-button_2.cjs.entry.js +3 -7
  9. package/dist/cjs/ion-input.cjs.entry.js +7 -15
  10. package/dist/cjs/ion-select_3.cjs.entry.js +56 -10
  11. package/dist/cjs/ion-textarea.cjs.entry.js +6 -14
  12. package/dist/cjs/ionic.cjs.js +1 -1
  13. package/dist/cjs/loader.cjs.js +1 -1
  14. package/dist/cjs/{notch-controller-Bzqhjm4f.js → validity-C8QoAYT2.js} +14 -0
  15. package/dist/collection/components/button/button.js +3 -7
  16. package/dist/collection/components/header/header.js +5 -4
  17. package/dist/collection/components/header/header.utils.js +37 -0
  18. package/dist/collection/components/input/input.js +6 -14
  19. package/dist/collection/components/select/select.js +59 -11
  20. package/dist/collection/components/textarea/textarea.js +5 -13
  21. package/dist/collection/utils/forms/index.js +1 -0
  22. package/dist/collection/utils/forms/validity.js +15 -0
  23. package/dist/docs.json +1 -1
  24. package/dist/esm/ion-app_8.entry.js +41 -3
  25. package/dist/esm/ion-button_2.entry.js +3 -7
  26. package/dist/esm/ion-input.entry.js +6 -14
  27. package/dist/esm/ion-select_3.entry.js +55 -9
  28. package/dist/esm/ion-textarea.entry.js +5 -13
  29. package/dist/esm/ionic.js +1 -1
  30. package/dist/esm/loader.js +1 -1
  31. package/dist/esm/{notch-controller-BwelN_JM.js → validity-B8oWougr.js} +14 -1
  32. package/dist/ionic/ionic.esm.js +1 -1
  33. package/dist/ionic/p-43ed1ef5.entry.js +4 -0
  34. package/dist/ionic/p-4c85d268.entry.js +4 -0
  35. package/dist/ionic/p-4cc26913.entry.js +4 -0
  36. package/dist/ionic/p-8bdfc8f6.entry.js +4 -0
  37. package/dist/ionic/{p-DCv9sLH2.js → p-DieJyvMP.js} +1 -1
  38. package/dist/ionic/p-f65f9308.entry.js +4 -0
  39. package/dist/types/components/header/header.utils.d.ts +10 -0
  40. package/dist/types/components/input/input.d.ts +0 -4
  41. package/dist/types/components/select/select.d.ts +6 -0
  42. package/dist/types/components/textarea/textarea.d.ts +0 -4
  43. package/dist/types/utils/forms/index.d.ts +1 -0
  44. package/dist/types/utils/forms/validity.d.ts +10 -0
  45. package/hydrate/index.js +86 -41
  46. package/hydrate/index.mjs +86 -41
  47. package/package.json +2 -2
  48. package/dist/ionic/p-1c8a476d.entry.js +0 -4
  49. package/dist/ionic/p-7647da93.entry.js +0 -4
  50. package/dist/ionic/p-785026d7.entry.js +0 -4
  51. package/dist/ionic/p-78c74a3e.entry.js +0 -4
  52. package/dist/ionic/p-913a7c1e.entry.js +0 -4
package/hydrate/index.js CHANGED
@@ -9263,11 +9263,7 @@ class Button {
9263
9263
  target,
9264
9264
  };
9265
9265
  let fill = this.fill;
9266
- /**
9267
- * We check both undefined and null to
9268
- * work around https://github.com/ionic-team/stencil/issues/3586.
9269
- */
9270
- if (fill == null) {
9266
+ if (fill === undefined) {
9271
9267
  fill = this.inToolbar || this.inListHeader ? 'clear' : 'solid';
9272
9268
  }
9273
9269
  /**
@@ -9280,7 +9276,7 @@ class Button {
9280
9276
  {
9281
9277
  type !== 'button' && this.renderHiddenButton();
9282
9278
  }
9283
- return (hAsync(Host, { key: 'b105ad09215adb3ca2298acdadf0dc9154bbb9b0', onClick: this.handleClick, "aria-disabled": disabled ? 'true' : null, class: createColorClasses$1(color, {
9279
+ return (hAsync(Host, { key: 'ed82ea53705523f9afc5f1a9addff44cc6424f27', onClick: this.handleClick, "aria-disabled": disabled ? 'true' : null, class: createColorClasses$1(color, {
9284
9280
  [mode]: true,
9285
9281
  [buttonType]: true,
9286
9282
  [`${buttonType}-${expand}`]: expand !== undefined,
@@ -9295,7 +9291,7 @@ class Button {
9295
9291
  'button-disabled': disabled,
9296
9292
  'ion-activatable': true,
9297
9293
  'ion-focusable': true,
9298
- }) }, hAsync(TagType, Object.assign({ key: '66b4e7112bcb9e41d5a723fbbadb0a3104f9ee1d' }, attrs, { class: "button-native", part: "native", disabled: disabled, onFocus: this.onFocus, onBlur: this.onBlur }, inheritedAttributes), hAsync("span", { key: '1439fc3da280221028dcf7ce8ec9dab273c4d4bb', class: "button-inner" }, hAsync("slot", { key: 'd5269ae1afc87ec7b99746032f59cbae93720a9f', name: "icon-only", onSlotchange: this.slotChanged }), hAsync("slot", { key: '461c83e97aa246aa86d83e14f1e15a288d35041e', name: "start" }), hAsync("slot", { key: '807170d47101f9f6a333dd4ff489c89284f306fe' }), hAsync("slot", { key: 'e67f116dd0349a0d27893e4f3ff0ccef1d402f80', name: "end" })), mode === 'md' && hAsync("ion-ripple-effect", { key: '273f0bd9645a36c1bfd18a5c2ab4f81e22b7b989', type: this.rippleType }))));
9294
+ }) }, hAsync(TagType, Object.assign({ key: 'fadec13053469dd0405bbbc61b70ced568aa4826' }, attrs, { class: "button-native", part: "native", disabled: disabled, onFocus: this.onFocus, onBlur: this.onBlur }, inheritedAttributes), hAsync("span", { key: '6bf0e5144fb1148002e88038522402b789689d2c', class: "button-inner" }, hAsync("slot", { key: '25da0ca155cfa9e2754842c34f4fd09f576ac2d2', name: "icon-only", onSlotchange: this.slotChanged }), hAsync("slot", { key: '51414065bb11953ec9d818f8d9353589bc9072c5', name: "start" }), hAsync("slot", { key: 'c9b5f8842aeabd20628df2f4600f1257ea913d8d' }), hAsync("slot", { key: '478dd3671c7be1909fc84e672f0fa8dfe6082263', name: "end" })), mode === 'md' && hAsync("ion-ripple-effect", { key: 'e1d55f85a55144d743f58a5914cd116cb065fa8c', type: this.rippleType }))));
9299
9295
  }
9300
9296
  get el() { return getElement(this); }
9301
9297
  static get watchers() { return {
@@ -15229,6 +15225,8 @@ class Grid {
15229
15225
  }
15230
15226
 
15231
15227
  const TRANSITION = 'all 0.2s ease-in-out';
15228
+ const ROLE_NONE = 'none';
15229
+ const ROLE_BANNER = 'banner';
15232
15230
  const cloneElement = (tagName) => {
15233
15231
  const getCachedEl = document.querySelector(`${tagName}.ion-cloned-element`);
15234
15232
  if (getCachedEl !== null) {
@@ -15355,6 +15353,7 @@ const setHeaderActive = (headerIndex, active = true) => {
15355
15353
  const toolbars = headerIndex.toolbars;
15356
15354
  const ionTitles = toolbars.map((toolbar) => toolbar.ionTitleEl);
15357
15355
  if (active) {
15356
+ headerEl.setAttribute('role', ROLE_BANNER);
15358
15357
  headerEl.classList.remove('header-collapse-condense-inactive');
15359
15358
  ionTitles.forEach((ionTitle) => {
15360
15359
  if (ionTitle) {
@@ -15363,6 +15362,16 @@ const setHeaderActive = (headerIndex, active = true) => {
15363
15362
  });
15364
15363
  }
15365
15364
  else {
15365
+ /**
15366
+ * There can only be one banner landmark per page.
15367
+ * By default, all ion-headers have the banner role.
15368
+ * This causes an accessibility issue when using a
15369
+ * condensed header since there are two ion-headers
15370
+ * on the page at once (active and inactive).
15371
+ * To solve this, the role needs to be toggled
15372
+ * based on which header is active.
15373
+ */
15374
+ headerEl.setAttribute('role', ROLE_NONE);
15366
15375
  headerEl.classList.add('header-collapse-condense-inactive');
15367
15376
  /**
15368
15377
  * The small title should only be accessed by screen readers
@@ -15422,6 +15431,30 @@ const handleHeaderFade = (scrollEl, baseEl, condenseHeader) => {
15422
15431
  });
15423
15432
  });
15424
15433
  };
15434
+ /**
15435
+ * Get the role type for the ion-header.
15436
+ *
15437
+ * @param isInsideMenu If ion-header is inside ion-menu.
15438
+ * @param isCondensed If ion-header has collapse="condense".
15439
+ * @param mode The current mode.
15440
+ * @returns 'none' if inside ion-menu or if condensed in md
15441
+ * mode, otherwise 'banner'.
15442
+ */
15443
+ const getRoleType = (isInsideMenu, isCondensed, mode) => {
15444
+ // If the header is inside a menu, it should not have the banner role.
15445
+ if (isInsideMenu) {
15446
+ return ROLE_NONE;
15447
+ }
15448
+ /**
15449
+ * Only apply role="none" to `md` mode condensed headers
15450
+ * since the large header is never shown.
15451
+ */
15452
+ if (isCondensed && mode === 'md') {
15453
+ return ROLE_NONE;
15454
+ }
15455
+ // Default to banner role.
15456
+ return ROLE_BANNER;
15457
+ };
15425
15458
 
15426
15459
  const headerIosCss = "ion-header{display:block;position:relative;-ms-flex-order:-1;order:-1;width:100%;z-index:10}ion-header ion-toolbar:first-of-type{padding-top:var(--ion-safe-area-top, 0)}.header-ios ion-toolbar:last-of-type{--border-width:0 0 0.55px}@supports ((-webkit-backdrop-filter: blur(0)) or (backdrop-filter: blur(0))){.header-background{left:0;right:0;top:0;bottom:0;position:absolute;-webkit-backdrop-filter:saturate(180%) blur(20px);backdrop-filter:saturate(180%) blur(20px)}.header-translucent-ios ion-toolbar{--opacity:.8}.header-collapse-condense-inactive .header-background{-webkit-backdrop-filter:blur(20px);backdrop-filter:blur(20px)}}.header-ios.ion-no-border ion-toolbar:last-of-type{--border-width:0}.header-collapse-fade ion-toolbar{--opacity-scale:inherit}.header-collapse-fade.header-transitioning ion-toolbar{--background:transparent;--border-style:none}.header-collapse-condense{z-index:9}.header-collapse-condense ion-toolbar{position:-webkit-sticky;position:sticky;top:0}.header-collapse-condense ion-toolbar:first-of-type{padding-top:0px;z-index:1}.header-collapse-condense ion-toolbar{z-index:0}.header-collapse-condense ion-toolbar:last-of-type{--border-width:0px}.header-collapse-condense ion-toolbar ion-searchbar{padding-top:0px;padding-bottom:13px}.header-collapse-main{--opacity-scale:1}.header-collapse-main ion-toolbar{--opacity-scale:inherit}.header-collapse-main ion-toolbar.in-toolbar ion-title,.header-collapse-main ion-toolbar.in-toolbar ion-buttons{-webkit-transition:all 0.2s ease-in-out;transition:all 0.2s ease-in-out}.header-collapse-condense ion-toolbar,.header-collapse-condense-inactive.header-transitioning:not(.header-collapse-condense) ion-toolbar{--background:var(--ion-background-color, #fff)}.header-collapse-condense-inactive.header-transitioning:not(.header-collapse-condense) ion-toolbar{--border-style:none;--opacity-scale:1}.header-collapse-condense-inactive:not(.header-collapse-condense) ion-toolbar.in-toolbar ion-title,.header-collapse-condense-inactive:not(.header-collapse-condense) ion-toolbar.in-toolbar ion-buttons.buttons-collapse{opacity:0;pointer-events:none}.header-collapse-condense-inactive.header-collapse-condense ion-toolbar.in-toolbar ion-title,.header-collapse-condense-inactive.header-collapse-condense ion-toolbar.in-toolbar ion-buttons.buttons-collapse{visibility:hidden}ion-header.header-ios:not(.header-collapse-main):has(~ion-content ion-header.header-ios[collapse=condense],~ion-content ion-header.header-ios.header-collapse-condense){opacity:0}";
15427
15460
 
@@ -15566,16 +15599,17 @@ class Header {
15566
15599
  const { translucent, inheritedAttributes } = this;
15567
15600
  const mode = getIonMode$1(this);
15568
15601
  const collapse = this.collapse || 'none';
15602
+ const isCondensed = collapse === 'condense';
15569
15603
  // banner role must be at top level, so remove role if inside a menu
15570
- const roleType = hostContext('ion-menu', this.el) ? 'none' : 'banner';
15571
- return (hAsync(Host, Object.assign({ key: 'b6cc27f0b08afc9fcc889683525da765d80ba672', role: roleType, class: {
15604
+ const roleType = getRoleType(hostContext('ion-menu', this.el), isCondensed, mode);
15605
+ return (hAsync(Host, Object.assign({ key: '863c4568cd7b8c0ec55109f193bbbaed68a1346e', role: roleType, class: {
15572
15606
  [mode]: true,
15573
15607
  // Used internally for styling
15574
15608
  [`header-${mode}`]: true,
15575
15609
  [`header-translucent`]: this.translucent,
15576
15610
  [`header-collapse-${collapse}`]: true,
15577
15611
  [`header-translucent-${mode}`]: this.translucent,
15578
- } }, inheritedAttributes), mode === 'ios' && translucent && hAsync("div", { key: '395766d4dcee3398bc91960db21f922095292f14', class: "header-background" }), hAsync("slot", { key: '09a67ece27b258ff1248805d43d92a49b2c6859a' })));
15612
+ } }, inheritedAttributes), mode === 'ios' && translucent && hAsync("div", { key: '25c3bdce328b0b35607d154c8b8374679313d881', class: "header-background" }), hAsync("slot", { key: 'b44fab0a9be7920b9650da26117c783e751e1702' })));
15579
15613
  }
15580
15614
  get el() { return getElement(this); }
15581
15615
  static get style() { return {
@@ -16387,6 +16421,19 @@ const isOptionSelected = (currentValue, compareValue, compareWith) => {
16387
16421
  }
16388
16422
  };
16389
16423
 
16424
+ /**
16425
+ * Checks if the form element is in an invalid state based on
16426
+ * Ionic validation classes.
16427
+ *
16428
+ * @param el The form element to check.
16429
+ * @returns `true` if the element is invalid, `false` otherwise.
16430
+ */
16431
+ const checkInvalidState = (el) => {
16432
+ const hasIonTouched = el.classList.contains('ion-touched');
16433
+ const hasIonInvalid = el.classList.contains('ion-invalid');
16434
+ return hasIonTouched && hasIonInvalid;
16435
+ };
16436
+
16390
16437
  /**
16391
16438
  * Used to update a scoped component that uses emulated slots. This fires when
16392
16439
  * content is passed into the slot or when the content inside of a slot changes.
@@ -16752,20 +16799,12 @@ class Input {
16752
16799
  componentWillLoad() {
16753
16800
  this.inheritedAttributes = Object.assign(Object.assign({}, inheritAriaAttributes(this.el)), inheritAttributes$1(this.el, ['tabindex', 'title', 'data-form-type', 'dir']));
16754
16801
  }
16755
- /**
16756
- * Checks if the input is in an invalid state based on Ionic validation classes
16757
- */
16758
- checkInvalidState() {
16759
- const hasIonTouched = this.el.classList.contains('ion-touched');
16760
- const hasIonInvalid = this.el.classList.contains('ion-invalid');
16761
- return hasIonTouched && hasIonInvalid;
16762
- }
16763
16802
  connectedCallback() {
16764
16803
  const { el } = this;
16765
16804
  this.slotMutationController = createSlotMutationController(el, ['label', 'start', 'end'], () => forceUpdate());
16766
16805
  this.notchController = createNotchController(el, () => this.notchSpacerEl, () => this.labelSlot);
16767
16806
  // Always set initial state
16768
- this.isInvalid = this.checkInvalidState();
16807
+ this.isInvalid = checkInvalidState(el);
16769
16808
  this.debounceChanged();
16770
16809
  }
16771
16810
  componentDidLoad() {
@@ -17019,7 +17058,7 @@ class Input {
17019
17058
  * TODO(FW-5592): Remove hasStartEndSlots condition
17020
17059
  */
17021
17060
  const labelShouldFloat = labelPlacement === 'stacked' || (labelPlacement === 'floating' && (hasValue || hasFocus || hasStartEndSlots));
17022
- return (hAsync(Host, { key: '8a51f0300d5bc66392f9ab9a6fa0b5d388072a33', class: createColorClasses$1(this.color, {
17061
+ return (hAsync(Host, { key: '97b5308021064d9e7434ef2d3d96f27045c1b0c4', class: createColorClasses$1(this.color, {
17023
17062
  [mode]: true,
17024
17063
  'has-value': hasValue,
17025
17064
  'has-focus': hasFocus,
@@ -17030,14 +17069,14 @@ class Input {
17030
17069
  'in-item': inItem,
17031
17070
  'in-item-color': hostContext('ion-item.ion-color', this.el),
17032
17071
  'input-disabled': disabled,
17033
- }) }, hAsync("label", { key: '9f8cf88d7d0e27931b51bd9c67f048c7fc6f5703', class: "input-wrapper", htmlFor: inputId, onClick: this.onLabelClick }, this.renderLabelContainer(), hAsync("div", { key: '7ad30bf9777774062a6ccf9a3ba804f251eef1bb', class: "native-wrapper", onClick: this.onLabelClick }, hAsync("slot", { key: '8af0b0325d101df8eed7d24f2767d6ca4d307319', name: "start" }), hAsync("input", Object.assign({ key: '1c53f7f9fa2567f3df19681cf4e7c21be382eae6', 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 && (hAsync("button", { key: 'b081d0e1ec1444b4c9cca145fc9cd2ad4a68b3da', "aria-label": "reset", type: "button", class: "input-clear-icon", onPointerDown: (ev) => {
17072
+ }) }, hAsync("label", { key: '353f68726ce180299bd9adc81e5ff7d26a48f54f', class: "input-wrapper", htmlFor: inputId, onClick: this.onLabelClick }, this.renderLabelContainer(), hAsync("div", { key: '2034b4bad04fc157f3298a1805819216b6f439d0', class: "native-wrapper", onClick: this.onLabelClick }, hAsync("slot", { key: '96bb5e30176b2bd76dfb75bfbf6c1c3d4403f4bb', name: "start" }), hAsync("input", Object.assign({ key: '1a1d75b0e414a95c89d5a760757c33548d234aca', 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 && (hAsync("button", { key: '95f3df17b7691d9a2e7dcd4a51f16a94aa3ca36f', "aria-label": "reset", type: "button", class: "input-clear-icon", onPointerDown: (ev) => {
17034
17073
  /**
17035
17074
  * This prevents mobile browsers from
17036
17075
  * blurring the input when the clear
17037
17076
  * button is activated.
17038
17077
  */
17039
17078
  ev.preventDefault();
17040
- }, onClick: this.clearTextInput }, hAsync("ion-icon", { key: '01535299241c3635460c05646420acf62a1ff567', "aria-hidden": "true", icon: clearIconData }))), hAsync("slot", { key: '480f3eb58b08ae792866a5b9b4c068748c5567cc', name: "end" })), shouldRenderHighlight && hAsync("div", { key: 'a8609cacee88e4a09f1cca65b6a47cb79a56f35e', class: "input-highlight" })), this.renderBottomContent()));
17079
+ }, onClick: this.clearTextInput }, hAsync("ion-icon", { key: '16b0af75eed50c8115fb5597f73b5fbf71c2530e', "aria-hidden": "true", icon: clearIconData }))), hAsync("slot", { key: 'c48da0f8ddb3764ac43efa705bb4a6bb2d9cc2fd', name: "end" })), shouldRenderHighlight && hAsync("div", { key: 'f15238481fc20de56ca7ecb6e350b3c024cc755e', class: "input-highlight" })), this.renderBottomContent()));
17041
17080
  }
17042
17081
  get el() { return getElement(this); }
17043
17082
  static get watchers() { return {
@@ -33129,6 +33168,10 @@ class Select {
33129
33168
  * is applied in both cases.
33130
33169
  */
33131
33170
  this.hasFocus = false;
33171
+ /**
33172
+ * Track validation state for proper aria-live announcements.
33173
+ */
33174
+ this.isInvalid = false;
33132
33175
  /**
33133
33176
  * The text to display on the cancel button.
33134
33177
  */
@@ -33251,9 +33294,12 @@ class Select {
33251
33294
  this.mutationO = watchForOptions(this.el, 'ion-select-option', async () => {
33252
33295
  this.updateOverlayOptions();
33253
33296
  });
33297
+ // Always set initial state
33298
+ this.isInvalid = checkInvalidState(this.el);
33254
33299
  }
33255
33300
  componentWillLoad() {
33256
33301
  this.inheritedAttributes = inheritAttributes$1(this.el, ['aria-label']);
33302
+ this.hintTextID = this.getHintTextID();
33257
33303
  }
33258
33304
  componentDidLoad() {
33259
33305
  /**
@@ -33277,6 +33323,11 @@ class Select {
33277
33323
  this.notchController.destroy();
33278
33324
  this.notchController = undefined;
33279
33325
  }
33326
+ // Clean up validation observer to prevent memory leaks.
33327
+ if (this.validationObserver) {
33328
+ this.validationObserver.disconnect();
33329
+ this.validationObserver = undefined;
33330
+ }
33280
33331
  }
33281
33332
  /**
33282
33333
  * Open the select overlay. The overlay is either an alert, action sheet, or popover,
@@ -33747,11 +33798,11 @@ class Select {
33747
33798
  }
33748
33799
  renderListbox() {
33749
33800
  const { disabled, inputId, isExpanded, required } = this;
33750
- return (hAsync("button", { disabled: disabled, id: inputId, "aria-label": this.ariaLabel, "aria-haspopup": "dialog", "aria-expanded": `${isExpanded}`, "aria-describedby": this.getHintTextID(), "aria-invalid": this.getHintTextID() === this.errorTextId, "aria-required": `${required}`, onFocus: this.onFocus, onBlur: this.onBlur, ref: (focusEl) => (this.focusEl = focusEl) }));
33801
+ return (hAsync("button", { disabled: disabled, id: inputId, "aria-label": this.ariaLabel, "aria-haspopup": "dialog", "aria-expanded": `${isExpanded}`, "aria-describedby": this.hintTextID, "aria-invalid": this.isInvalid ? 'true' : undefined, "aria-required": `${required}`, onFocus: this.onFocus, onBlur: this.onBlur, ref: (focusEl) => (this.focusEl = focusEl) }));
33751
33802
  }
33752
33803
  getHintTextID() {
33753
- const { el, helperText, errorText, helperTextId, errorTextId } = this;
33754
- if (el.classList.contains('ion-touched') && el.classList.contains('ion-invalid') && errorText) {
33804
+ const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
33805
+ if (isInvalid && errorText) {
33755
33806
  return errorTextId;
33756
33807
  }
33757
33808
  if (helperText) {
@@ -33763,10 +33814,10 @@ class Select {
33763
33814
  * Renders the helper text or error text values
33764
33815
  */
33765
33816
  renderHintText() {
33766
- const { helperText, errorText, helperTextId, errorTextId } = this;
33817
+ const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
33767
33818
  return [
33768
- hAsync("div", { id: helperTextId, class: "helper-text", part: "supporting-text helper-text" }, helperText),
33769
- hAsync("div", { id: errorTextId, class: "error-text", part: "supporting-text error-text" }, errorText),
33819
+ hAsync("div", { id: helperTextId, class: "helper-text", part: "supporting-text helper-text", "aria-live": "polite" }, !isInvalid ? helperText : null),
33820
+ hAsync("div", { id: errorTextId, class: "error-text", part: "supporting-text error-text", role: "alert" }, isInvalid ? errorText : null),
33770
33821
  ];
33771
33822
  }
33772
33823
  /**
@@ -33814,7 +33865,7 @@ class Select {
33814
33865
  * TODO(FW-5592): Remove hasStartEndSlots condition
33815
33866
  */
33816
33867
  const labelShouldFloat = labelPlacement === 'stacked' || (labelPlacement === 'floating' && (hasValue || isExpanded || hasStartEndSlots));
33817
- return (hAsync(Host, { key: 'c03fb65e8fc9f9aab295e07b282377d57d910519', onClick: this.onClick, class: createColorClasses$1(this.color, {
33868
+ return (hAsync(Host, { key: '35b5e18e6f79a802ff2d46d1242e80ff755cc0b9', onClick: this.onClick, class: createColorClasses$1(this.color, {
33818
33869
  [mode]: true,
33819
33870
  'in-item': inItem,
33820
33871
  'in-item-color': hostContext('ion-item.ion-color', el),
@@ -33832,7 +33883,7 @@ class Select {
33832
33883
  [`select-justify-${justify}`]: justifyEnabled,
33833
33884
  [`select-shape-${shape}`]: shape !== undefined,
33834
33885
  [`select-label-placement-${labelPlacement}`]: true,
33835
- }) }, hAsync("label", { key: '0d0c8ec55269adcac625f2899a547f4e7f3e3741', class: "select-wrapper", id: "select-label", onClick: this.onLabelClick }, this.renderLabelContainer(), hAsync("div", { key: 'f6dfc93c0e23cbe75a2947abde67d842db2dad78', class: "select-wrapper-inner" }, hAsync("slot", { key: '957bfadf9f101f519091419a362d3abdc2be66f6', name: "start" }), hAsync("div", { key: 'ca349202a484e7f2e884533fd330f0b136754f7d', class: "native-wrapper", ref: (el) => (this.nativeWrapperEl = el), part: "container" }, this.renderSelectText(), this.renderListbox()), hAsync("slot", { key: 'f0e62a6533ff1c8f62bd2d27f60b23385c4fa9ed', name: "end" }), !hasFloatingOrStackedLabel && this.renderSelectIcon()), hasFloatingOrStackedLabel && this.renderSelectIcon(), shouldRenderHighlight && hAsync("div", { key: 'fb840d46bafafb09898ebeebbe8c181906a3d8a2', class: "select-highlight" })), this.renderBottomContent()));
33886
+ }) }, hAsync("label", { key: '6005b34a0c50bc4d7653a4276bc232ecd02e083c', class: "select-wrapper", id: "select-label", onClick: this.onLabelClick }, this.renderLabelContainer(), hAsync("div", { key: 'c7e07aa81ae856c057f16275dd058f37c5670a47', class: "select-wrapper-inner" }, hAsync("slot", { key: '7fc2deefe0424404caacdbbd9e08ed43ba55d28a', name: "start" }), hAsync("div", { key: '157d74ee717b1bc30b5f1c233a09b0c8456aa68e', class: "native-wrapper", ref: (el) => (this.nativeWrapperEl = el), part: "container" }, this.renderSelectText(), this.renderListbox()), hAsync("slot", { key: 'ea66db304528b82bf9317730b6dce3db2612f235', name: "end" }), !hasFloatingOrStackedLabel && this.renderSelectIcon()), hasFloatingOrStackedLabel && this.renderSelectIcon(), shouldRenderHighlight && hAsync("div", { key: '786eb1530b7476f0615d4e7c0bf4e7e4dc66509c', class: "select-highlight" })), this.renderBottomContent()));
33836
33887
  }
33837
33888
  get el() { return getElement(this); }
33838
33889
  static get watchers() { return {
@@ -33873,6 +33924,8 @@ class Select {
33873
33924
  "required": [4],
33874
33925
  "isExpanded": [32],
33875
33926
  "hasFocus": [32],
33927
+ "isInvalid": [32],
33928
+ "hintTextID": [32],
33876
33929
  "open": [64]
33877
33930
  },
33878
33931
  "$listeners$": undefined,
@@ -35060,20 +35113,12 @@ class Textarea {
35060
35113
  this.el.click();
35061
35114
  }
35062
35115
  }
35063
- /**
35064
- * Checks if the textarea is in an invalid state based on Ionic validation classes
35065
- */
35066
- checkValidationState() {
35067
- const hasIonTouched = this.el.classList.contains('ion-touched');
35068
- const hasIonInvalid = this.el.classList.contains('ion-invalid');
35069
- return hasIonTouched && hasIonInvalid;
35070
- }
35071
35116
  connectedCallback() {
35072
35117
  const { el } = this;
35073
35118
  this.slotMutationController = createSlotMutationController(el, ['label', 'start', 'end'], () => forceUpdate());
35074
35119
  this.notchController = createNotchController(el, () => this.notchSpacerEl, () => this.labelSlot);
35075
35120
  // Always set initial state
35076
- this.isInvalid = this.checkValidationState();
35121
+ this.isInvalid = checkInvalidState(this.el);
35077
35122
  this.debounceChanged();
35078
35123
  }
35079
35124
  disconnectedCallback() {
@@ -35327,7 +35372,7 @@ class Textarea {
35327
35372
  * TODO(FW-5592): Remove hasStartEndSlots condition
35328
35373
  */
35329
35374
  const labelShouldFloat = labelPlacement === 'stacked' || (labelPlacement === 'floating' && (hasValue || hasFocus || hasStartEndSlots));
35330
- return (hAsync(Host, { key: '26b46666a92b3f652775bb1c46661f9a30392104', class: createColorClasses$1(this.color, {
35375
+ return (hAsync(Host, { key: 'a70a62d7aae3831a50acd74f60b930925ada1326', class: createColorClasses$1(this.color, {
35331
35376
  [mode]: true,
35332
35377
  'has-value': hasValue,
35333
35378
  'has-focus': hasFocus,
@@ -35336,7 +35381,7 @@ class Textarea {
35336
35381
  [`textarea-shape-${shape}`]: shape !== undefined,
35337
35382
  [`textarea-label-placement-${labelPlacement}`]: true,
35338
35383
  'textarea-disabled': disabled,
35339
- }) }, hAsync("label", { key: '2649da816216959ebe1f34cafd9dedbac20ec3c2', class: "textarea-wrapper", htmlFor: inputId, onClick: this.onLabelClick }, this.renderLabelContainer(), hAsync("div", { key: 'dca98593efece1b044dbcda045fa70882d715cb2', class: "textarea-wrapper-inner" }, hAsync("div", { key: '2019daf87fddca5ec0b2e336f0376fd9642bae1b', class: "start-slot-wrapper" }, hAsync("slot", { key: '36c423c394a71d08261705b9d6729e756bf65924', name: "start" })), hAsync("div", { key: '0c3ea34105c7eddfa4094371c5d288c50ed10db3', class: "native-wrapper", ref: (el) => (this.textareaWrapper = el) }, hAsync("textarea", Object.assign({ key: 'ce173b83b16aff43d293fa1edef9b66c6676227b', 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)), hAsync("div", { key: '756e343cfd208bb5ad9ecf08d77cbb0a9606dc7b', class: "end-slot-wrapper" }, hAsync("slot", { key: '0eb596814a037fa4634ff8c5bac0045540edfe21', name: "end" }))), shouldRenderHighlight && hAsync("div", { key: 'df62f896eb6e0e2d1217aa487c198eb82a52bcb8', class: "textarea-highlight" })), this.renderBottomContent()));
35384
+ }) }, hAsync("label", { key: '8a2dd59a60f7469df84018eb0ede3a9ec3862703', class: "textarea-wrapper", htmlFor: inputId, onClick: this.onLabelClick }, this.renderLabelContainer(), hAsync("div", { key: '1bfc368236e3da7a225a45118c27fbfc1fe5fa46', class: "textarea-wrapper-inner" }, hAsync("div", { key: '215cbb2635ff52e31a8973376989b85e7245d40f', class: "start-slot-wrapper" }, hAsync("slot", { key: '9f6b461cdee9d629deb695d2bea054ece2f32305', name: "start" })), hAsync("div", { key: 'c1af35a2d5bc452bebe0b22a26d15ff52b4e9fc8', class: "native-wrapper", ref: (el) => (this.textareaWrapper = el) }, hAsync("textarea", Object.assign({ key: '69a69b3cf0932baafbe37e6e846f1a571608d3f2', 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)), hAsync("div", { key: 'c053ea8b865d0e29763aed2e4939cc9c9e374c15', class: "end-slot-wrapper" }, hAsync("slot", { key: '930aa641833b0df54b9ea10368fc2f46d5f491f6', name: "end" }))), shouldRenderHighlight && hAsync("div", { key: '8d12597d15f5f429d80e8272ea99e64ed924e482', class: "textarea-highlight" })), this.renderBottomContent()));
35340
35385
  }
35341
35386
  get el() { return getElement(this); }
35342
35387
  static get watchers() { return {