@ionic/core 8.7.3-dev.11755600455.1e79c35a → 8.7.3-dev.11755622775.1fc05815

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 (86) hide show
  1. package/components/ion-datetime.js +3 -4
  2. package/components/ion-input.js +36 -5
  3. package/components/ion-refresher.js +18 -1
  4. package/components/ion-textarea.js +35 -4
  5. package/components/overlays.js +97 -3
  6. package/components/picker-column-option.js +3 -3
  7. package/components/picker-column.js +39 -3
  8. package/dist/cjs/index.cjs.js +1 -1
  9. package/dist/cjs/ion-action-sheet.cjs.entry.js +1 -1
  10. package/dist/cjs/ion-alert.cjs.entry.js +1 -1
  11. package/dist/cjs/ion-datetime_3.cjs.entry.js +4 -4
  12. package/dist/cjs/ion-input.cjs.entry.js +35 -5
  13. package/dist/cjs/ion-loading.cjs.entry.js +1 -1
  14. package/dist/cjs/ion-menu_3.cjs.entry.js +1 -1
  15. package/dist/cjs/ion-modal.cjs.entry.js +1 -1
  16. package/dist/cjs/ion-picker-column-option.cjs.entry.js +3 -3
  17. package/dist/cjs/ion-picker-column.cjs.entry.js +39 -3
  18. package/dist/cjs/ion-popover.cjs.entry.js +1 -1
  19. package/dist/cjs/ion-refresher_2.cjs.entry.js +18 -1
  20. package/dist/cjs/ion-select-modal.cjs.entry.js +1 -1
  21. package/dist/cjs/ion-select_3.cjs.entry.js +1 -1
  22. package/dist/cjs/ion-textarea.cjs.entry.js +34 -4
  23. package/dist/cjs/ion-toast.cjs.entry.js +1 -1
  24. package/dist/cjs/ionic.cjs.js +1 -1
  25. package/dist/cjs/loader.cjs.js +1 -1
  26. package/dist/cjs/{overlays-DUsEBICv.js → overlays-CglR7j-u.js} +96 -2
  27. package/dist/collection/components/datetime/datetime.js +3 -4
  28. package/dist/collection/components/input/input.js +37 -6
  29. package/dist/collection/components/picker-column/picker-column.js +39 -3
  30. package/dist/collection/components/picker-column-option/picker-column-option.ios.css +2 -2
  31. package/dist/collection/components/picker-column-option/picker-column-option.js +1 -1
  32. package/dist/collection/components/picker-column-option/picker-column-option.md.css +2 -2
  33. package/dist/collection/components/refresher/refresher.js +18 -1
  34. package/dist/collection/components/textarea/textarea.js +36 -5
  35. package/dist/collection/utils/overlays.js +97 -2
  36. package/dist/docs.json +1 -1
  37. package/dist/esm/index.js +1 -1
  38. package/dist/esm/ion-action-sheet.entry.js +1 -1
  39. package/dist/esm/ion-alert.entry.js +1 -1
  40. package/dist/esm/ion-datetime_3.entry.js +4 -4
  41. package/dist/esm/ion-input.entry.js +35 -5
  42. package/dist/esm/ion-loading.entry.js +1 -1
  43. package/dist/esm/ion-menu_3.entry.js +1 -1
  44. package/dist/esm/ion-modal.entry.js +1 -1
  45. package/dist/esm/ion-picker-column-option.entry.js +3 -3
  46. package/dist/esm/ion-picker-column.entry.js +39 -3
  47. package/dist/esm/ion-popover.entry.js +1 -1
  48. package/dist/esm/ion-refresher_2.entry.js +18 -1
  49. package/dist/esm/ion-select-modal.entry.js +1 -1
  50. package/dist/esm/ion-select_3.entry.js +1 -1
  51. package/dist/esm/ion-textarea.entry.js +34 -4
  52. package/dist/esm/ion-toast.entry.js +1 -1
  53. package/dist/esm/ionic.js +1 -1
  54. package/dist/esm/loader.js +1 -1
  55. package/dist/esm/{overlays-B_dsLNe8.js → overlays-ZX_4-t_r.js} +97 -3
  56. package/dist/ionic/index.esm.js +1 -1
  57. package/dist/ionic/ionic.esm.js +1 -1
  58. package/dist/ionic/p-187aaf4a.entry.js +4 -0
  59. package/dist/ionic/p-1d5b934a.entry.js +4 -0
  60. package/dist/ionic/p-1e34d434.entry.js +4 -0
  61. package/dist/ionic/p-29032e49.entry.js +4 -0
  62. package/dist/ionic/{p-8b54aa01.entry.js → p-7bac2c5f.entry.js} +1 -1
  63. package/dist/ionic/p-8cdb4ff5.entry.js +4 -0
  64. package/dist/ionic/{p-7ed24ba0.entry.js → p-8d96a0cd.entry.js} +1 -1
  65. package/dist/ionic/{p-84236acb.entry.js → p-91d6ccb0.entry.js} +1 -1
  66. package/dist/ionic/{p-09ed68cf.entry.js → p-92e8f208.entry.js} +1 -1
  67. package/dist/ionic/{p-98d0823e.entry.js → p-982fe1c4.entry.js} +1 -1
  68. package/dist/ionic/p-CSwZyt05.js +4 -0
  69. package/dist/ionic/{p-698fb72c.entry.js → p-ab33ef20.entry.js} +1 -1
  70. package/dist/ionic/{p-07d8f62a.entry.js → p-ac434970.entry.js} +1 -1
  71. package/dist/ionic/{p-57bb1214.entry.js → p-c575e7ce.entry.js} +1 -1
  72. package/dist/ionic/{p-9c6fddc6.entry.js → p-f2884bc2.entry.js} +1 -1
  73. package/dist/ionic/p-f456d176.entry.js +4 -0
  74. package/dist/types/components/input/input.d.ts +9 -0
  75. package/dist/types/components/picker-column/picker-column.d.ts +7 -0
  76. package/dist/types/components/textarea/textarea.d.ts +9 -0
  77. package/hydrate/index.js +204 -21
  78. package/hydrate/index.mjs +204 -21
  79. package/package.json +1 -1
  80. package/dist/ionic/p-1488b7cc.entry.js +0 -4
  81. package/dist/ionic/p-8888efe4.entry.js +0 -4
  82. package/dist/ionic/p-8bfe00f3.entry.js +0 -4
  83. package/dist/ionic/p-C3MD7jSK.js +0 -4
  84. package/dist/ionic/p-ac2be9d6.entry.js +0 -4
  85. package/dist/ionic/p-b292804d.entry.js +0 -4
  86. package/dist/ionic/p-c5210d3e.entry.js +0 -4
package/hydrate/index.js CHANGED
@@ -6167,11 +6167,9 @@ const setRootAriaHidden = (hidden = false) => {
6167
6167
  }
6168
6168
  if (hidden) {
6169
6169
  viewContainer.setAttribute('aria-hidden', 'true');
6170
- viewContainer.setAttribute('inert', '');
6171
6170
  }
6172
6171
  else {
6173
6172
  viewContainer.removeAttribute('aria-hidden');
6174
- viewContainer.removeAttribute('inert');
6175
6173
  }
6176
6174
  };
6177
6175
  const present = async (overlay, name, iosEnterAnimation, mdEnterAnimation, opts) => {
@@ -6190,6 +6188,8 @@ const present = async (overlay, name, iosEnterAnimation, mdEnterAnimation, opts)
6190
6188
  setRootAriaHidden(true);
6191
6189
  document.body.classList.add(BACKDROP_NO_SCROLL);
6192
6190
  }
6191
+ hideUnderlyingOverlaysFromScreenReaders(overlay.el);
6192
+ hideAnimatingOverlayFromScreenReaders(overlay.el);
6193
6193
  overlay.presented = true;
6194
6194
  overlay.willPresent.emit();
6195
6195
  (_a = overlay.willPresentShorthand) === null || _a === void 0 ? void 0 : _a.emit();
@@ -6315,6 +6315,12 @@ const dismiss = async (overlay, data, role, name, iosLeaveAnimation, mdLeaveAnim
6315
6315
  }
6316
6316
  overlay.presented = false;
6317
6317
  try {
6318
+ /**
6319
+ * There is no need to show the overlay to screen readers during
6320
+ * the dismiss animation. This is because the overlay will be removed
6321
+ * from the DOM after the animation is complete.
6322
+ */
6323
+ hideAnimatingOverlayFromScreenReaders(overlay.el);
6318
6324
  // Overlay contents should not be clickable during dismiss
6319
6325
  overlay.el.style.setProperty('pointer-events', 'none');
6320
6326
  overlay.willDismiss.emit({ data, role });
@@ -6353,6 +6359,7 @@ const dismiss = async (overlay, data, role, name, iosLeaveAnimation, mdLeaveAnim
6353
6359
  printIonError(`[${overlay.el.tagName.toLowerCase()}] - `, err);
6354
6360
  }
6355
6361
  overlay.el.remove();
6362
+ revealOverlaysToScreenReaders();
6356
6363
  return true;
6357
6364
  };
6358
6365
  const getAppRoot = (doc) => {
@@ -6548,6 +6555,93 @@ const createTriggerController = () => {
6548
6555
  removeClickListener,
6549
6556
  };
6550
6557
  };
6558
+ /**
6559
+ * The overlay that is being animated also needs to hide from screen
6560
+ * readers during its animation. This ensures that assistive technologies
6561
+ * like TalkBack do not announce or interact with the content until the
6562
+ * animation is complete, avoiding confusion for users.
6563
+ *
6564
+ * When the overlay is presented on an Android device, TalkBack's focus rings
6565
+ * may appear in the wrong position due to the transition (specifically
6566
+ * `transform` styles). This occurs because the focus rings are initially
6567
+ * displayed at the starting position of the elements before the transition
6568
+ * begins. This workaround ensures the focus rings do not appear in the
6569
+ * incorrect location.
6570
+ *
6571
+ * If this solution is applied to iOS devices, then it leads to a bug where
6572
+ * the overlays cannot be accessed by screen readers. This is due to
6573
+ * VoiceOver not being able to update the accessibility tree when the
6574
+ * `aria-hidden` is removed.
6575
+ *
6576
+ * @param overlay - The overlay that is being animated.
6577
+ */
6578
+ const hideAnimatingOverlayFromScreenReaders = (overlay) => {
6579
+ if (doc === undefined)
6580
+ return;
6581
+ if (isPlatform('android')) {
6582
+ /**
6583
+ * Once the animation is complete, this attribute will be removed.
6584
+ * This is done at the end of the `present` method.
6585
+ */
6586
+ overlay.setAttribute('aria-hidden', 'true');
6587
+ }
6588
+ };
6589
+ /**
6590
+ * Ensure that underlying overlays have aria-hidden if necessary so that screen readers
6591
+ * cannot move focus to these elements. Note that we cannot rely on focus/focusin/focusout
6592
+ * events here because those events do not fire when the screen readers moves to a non-focusable
6593
+ * element such as text.
6594
+ * Without this logic screen readers would be able to move focus outside of the top focus-trapped overlay.
6595
+ *
6596
+ * @param newTopMostOverlay - The overlay that is being presented. Since the overlay has not been
6597
+ * fully presented yet at the time this function is called it will not be included in the getPresentedOverlays result.
6598
+ */
6599
+ const hideUnderlyingOverlaysFromScreenReaders = (newTopMostOverlay) => {
6600
+ var _a;
6601
+ if (doc === undefined)
6602
+ return;
6603
+ const overlays = getPresentedOverlays(doc);
6604
+ for (let i = overlays.length - 1; i >= 0; i--) {
6605
+ const presentedOverlay = overlays[i];
6606
+ const nextPresentedOverlay = (_a = overlays[i + 1]) !== null && _a !== void 0 ? _a : newTopMostOverlay;
6607
+ /**
6608
+ * If next overlay has aria-hidden then all remaining overlays will have it too.
6609
+ * Or, if the next overlay is a Toast that does not have aria-hidden then current overlay
6610
+ * should not have aria-hidden either so focus can remain in the current overlay.
6611
+ */
6612
+ if (nextPresentedOverlay.hasAttribute('aria-hidden') || nextPresentedOverlay.tagName !== 'ION-TOAST') {
6613
+ presentedOverlay.setAttribute('aria-hidden', 'true');
6614
+ }
6615
+ }
6616
+ };
6617
+ /**
6618
+ * When dismissing an overlay we need to reveal the new top-most overlay to screen readers.
6619
+ * If the top-most overlay is a Toast we potentially need to reveal more overlays since
6620
+ * focus is never automatically moved to the Toast.
6621
+ */
6622
+ const revealOverlaysToScreenReaders = () => {
6623
+ if (doc === undefined)
6624
+ return;
6625
+ const overlays = getPresentedOverlays(doc);
6626
+ for (let i = overlays.length - 1; i >= 0; i--) {
6627
+ const currentOverlay = overlays[i];
6628
+ /**
6629
+ * If the current we are looking at is a Toast then we can remove aria-hidden.
6630
+ * However, we potentially need to keep looking at the overlay stack because there
6631
+ * could be more Toasts underneath. Additionally, we need to unhide the closest non-Toast
6632
+ * overlay too so focus can move there since focus is never automatically moved to the Toast.
6633
+ */
6634
+ currentOverlay.removeAttribute('aria-hidden');
6635
+ /**
6636
+ * If we found a non-Toast element then we can just remove aria-hidden and stop searching entirely
6637
+ * since this overlay should always receive focus. As a result, all underlying overlays should still
6638
+ * be hidden from screen readers.
6639
+ */
6640
+ if (currentOverlay.tagName !== 'ION-TOAST') {
6641
+ break;
6642
+ }
6643
+ }
6644
+ };
6551
6645
  const FOCUS_TRAP_DISABLE_CLASS = 'ion-disable-focus-trap';
6552
6646
 
6553
6647
  const hostContext = (selector, el) => {
@@ -13437,7 +13531,7 @@ class Datetime {
13437
13531
  const renderArray = forcePresentation === 'time-date'
13438
13532
  ? [this.renderTimePickerColumns(forcePresentation), this.renderDatePickerColumns(forcePresentation)]
13439
13533
  : [this.renderDatePickerColumns(forcePresentation), this.renderTimePickerColumns(forcePresentation)];
13440
- return hAsync("ion-picker", { class: FOCUS_TRAP_DISABLE_CLASS }, renderArray);
13534
+ return hAsync("ion-picker", null, renderArray);
13441
13535
  }
13442
13536
  renderDatePickerColumns(forcePresentation) {
13443
13537
  return forcePresentation === 'date-time' || forcePresentation === 'time-date'
@@ -13994,7 +14088,7 @@ class Datetime {
13994
14088
  const hasDatePresentation = presentation === 'date' || presentation === 'date-time' || presentation === 'time-date';
13995
14089
  const hasWheelVariant = hasDatePresentation && preferWheel;
13996
14090
  renderHiddenInput(true, el, name, formatValue(value), disabled);
13997
- return (hAsync(Host, { key: '57492534800ea059a7c2bbd9f0059cc0b75ae8d2', "aria-disabled": disabled ? 'true' : null, onFocus: this.onFocus, onBlur: this.onBlur, class: Object.assign({}, createColorClasses$1(color, {
14091
+ return (hAsync(Host, { key: 'f35cf200ff05d33074576e3d2754d3b2a0735b96', "aria-disabled": disabled ? 'true' : null, onFocus: this.onFocus, onBlur: this.onBlur, class: Object.assign({}, createColorClasses$1(color, {
13998
14092
  [mode]: true,
13999
14093
  ['datetime-readonly']: readonly,
14000
14094
  ['datetime-disabled']: disabled,
@@ -14004,7 +14098,7 @@ class Datetime {
14004
14098
  [`datetime-size-${size}`]: true,
14005
14099
  [`datetime-prefer-wheel`]: hasWheelVariant,
14006
14100
  [`datetime-grid`]: isGridStyle,
14007
- })) }, hAsync("div", { key: '97dac5e5195635ac0bc5fb472b9d09e5c3c6bbc3', class: "intersection-tracker", ref: (el) => (this.intersectionTrackerRef = el) }), this.renderDatetime(mode)));
14101
+ })) }, hAsync("div", { key: '859e9354a12bfa58ac3f964c2e66839f17071c00', class: "intersection-tracker", ref: (el) => (this.intersectionTrackerRef = el) }), this.renderDatetime(mode)));
14008
14102
  }
14009
14103
  get el() { return getElement(this); }
14010
14104
  static get watchers() { return {
@@ -16535,6 +16629,10 @@ class Input {
16535
16629
  * is applied in both cases.
16536
16630
  */
16537
16631
  this.hasFocus = false;
16632
+ /**
16633
+ * Track validation state for proper aria-live announcements
16634
+ */
16635
+ this.isInvalid = false;
16538
16636
  /**
16539
16637
  * Indicates whether and how the text value should be automatically capitalized as it is entered/edited by the user.
16540
16638
  * Available options: `"off"`, `"none"`, `"on"`, `"sentences"`, `"words"`, `"characters"`.
@@ -16718,10 +16816,18 @@ class Input {
16718
16816
  componentWillLoad() {
16719
16817
  this.inheritedAttributes = Object.assign(Object.assign({}, inheritAriaAttributes(this.el)), inheritAttributes$1(this.el, ['tabindex', 'title', 'data-form-type', 'dir']));
16720
16818
  }
16819
+ /**
16820
+ * Checks if the input is in an invalid state based on validation classes
16821
+ */
16822
+ checkValidationState() {
16823
+ return this.el.classList.contains('ion-touched') && this.el.classList.contains('ion-invalid');
16824
+ }
16721
16825
  connectedCallback() {
16722
16826
  const { el } = this;
16723
16827
  this.slotMutationController = createSlotMutationController(el, ['label', 'start', 'end'], () => forceUpdate());
16724
16828
  this.notchController = createNotchController(el, () => this.notchSpacerEl, () => this.labelSlot);
16829
+ // Always set initial state
16830
+ this.isInvalid = this.checkValidationState();
16725
16831
  this.debounceChanged();
16726
16832
  }
16727
16833
  componentDidLoad() {
@@ -16748,6 +16854,11 @@ class Input {
16748
16854
  this.notchController.destroy();
16749
16855
  this.notchController = undefined;
16750
16856
  }
16857
+ // Clean up validation observer to prevent memory leaks
16858
+ if (this.validationObserver) {
16859
+ this.validationObserver.disconnect();
16860
+ this.validationObserver = undefined;
16861
+ }
16751
16862
  }
16752
16863
  /**
16753
16864
  * Sets focus on the native `input` in `ion-input`. Use this method instead of the global
@@ -16856,8 +16967,8 @@ class Input {
16856
16967
  ];
16857
16968
  }
16858
16969
  getHintTextID() {
16859
- const { el, helperText, errorText, helperTextId, errorTextId } = this;
16860
- if (el.classList.contains('ion-touched') && el.classList.contains('ion-invalid') && errorText) {
16970
+ const { isInvalid, helperText, errorText, helperTextId, errorTextId } = this;
16971
+ if (isInvalid && errorText) {
16861
16972
  return errorTextId;
16862
16973
  }
16863
16974
  if (helperText) {
@@ -16970,7 +17081,7 @@ class Input {
16970
17081
  * TODO(FW-5592): Remove hasStartEndSlots condition
16971
17082
  */
16972
17083
  const labelShouldFloat = labelPlacement === 'stacked' || (labelPlacement === 'floating' && (hasValue || hasFocus || hasStartEndSlots));
16973
- return (hAsync(Host, { key: '41b2526627e7d2773a80f011b123284203a71ca0', class: createColorClasses$1(this.color, {
17084
+ return (hAsync(Host, { key: '1b0cffa910aef4eac24438bf3158e51905e70788', class: createColorClasses$1(this.color, {
16974
17085
  [mode]: true,
16975
17086
  'has-value': hasValue,
16976
17087
  'has-focus': hasFocus,
@@ -16981,14 +17092,14 @@ class Input {
16981
17092
  'in-item': inItem,
16982
17093
  'in-item-color': hostContext('ion-item.ion-color', this.el),
16983
17094
  'input-disabled': disabled,
16984
- }) }, hAsync("label", { key: '9ab078363e32528102b441ad1791d83f86fdcbdc', class: "input-wrapper", htmlFor: inputId, onClick: this.onLabelClick }, this.renderLabelContainer(), hAsync("div", { key: 'e34b594980ec62e4c618e827fadf7669a39ad0d8', class: "native-wrapper", onClick: this.onLabelClick }, hAsync("slot", { key: '12dc04ead5502e9e5736240e918bf9331bf7b5d9', name: "start" }), hAsync("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 && (hAsync("button", { key: 'f79f68cabcd4ea99419331174a377827db0c0741', "aria-label": "reset", type: "button", class: "input-clear-icon", onPointerDown: (ev) => {
17095
+ }) }, hAsync("label", { key: '24411a6cc190e2054015bca5e2827b96c65bba17', class: "input-wrapper", htmlFor: inputId, onClick: this.onLabelClick }, this.renderLabelContainer(), hAsync("div", { key: '7fa7a7f6d2778e6efbebc6f2671d8a63048f76a8', class: "native-wrapper", onClick: this.onLabelClick }, hAsync("slot", { key: '14f489e6026967ee1b2eee1519bad4230f028489', name: "start" }), hAsync("input", Object.assign({ key: 'd0f5b756394aced0831e80e91313a417bda37e6f', 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: '3e37003927082bbbfce4b3396a263969aa2289fc', "aria-label": "reset", type: "button", class: "input-clear-icon", onPointerDown: (ev) => {
16985
17096
  /**
16986
17097
  * This prevents mobile browsers from
16987
17098
  * blurring the input when the clear
16988
17099
  * button is activated.
16989
17100
  */
16990
17101
  ev.preventDefault();
16991
- }, onClick: this.clearTextInput }, hAsync("ion-icon", { key: '237ec07ec2e10f08818a332bb596578c2c49f770', "aria-hidden": "true", icon: clearIconData }))), hAsync("slot", { key: '1f0a3624aa3e8dc3c307a6762230ab698768a5e5', name: "end" })), shouldRenderHighlight && hAsync("div", { key: '8a8cbb82695a722a0010b53dd0b1f1f97534a20b', class: "input-highlight" })), this.renderBottomContent()));
17102
+ }, onClick: this.clearTextInput }, hAsync("ion-icon", { key: '269ccdfd95a8a5b39bf0587b1494b4badd5c65b3', "aria-hidden": "true", icon: clearIconData }))), hAsync("slot", { key: '5d93a350194c4197c01c5d54f50060e7f527f065', name: "end" })), shouldRenderHighlight && hAsync("div", { key: 'fbdd13d41b37aa1e4608ba2535f7b833748eae3f', class: "input-highlight" })), this.renderBottomContent()));
16992
17103
  }
16993
17104
  get el() { return getElement(this); }
16994
17105
  static get watchers() { return {
@@ -17040,6 +17151,7 @@ class Input {
17040
17151
  "type": [1],
17041
17152
  "value": [1032],
17042
17153
  "hasFocus": [32],
17154
+ "isInvalid": [32],
17043
17155
  "setFocus": [64],
17044
17156
  "getInputElement": [64]
17045
17157
  },
@@ -25410,6 +25522,23 @@ class PickerColumn {
25410
25522
  var _a;
25411
25523
  return el ? (_a = el.getAttribute('aria-label')) !== null && _a !== void 0 ? _a : el.innerText : '';
25412
25524
  };
25525
+ /**
25526
+ * Render an element that overlays the column. This element is for assistive
25527
+ * tech to allow users to navigate the column up/down. This element should receive
25528
+ * focus as it listens for synthesized keyboard events as required by the
25529
+ * slider role: https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/slider_role
25530
+ */
25531
+ this.renderAssistiveFocusable = () => {
25532
+ const { activeItem } = this;
25533
+ const valueText = this.getOptionValueText(activeItem);
25534
+ /**
25535
+ * When using the picker, the valuetext provides important context that valuenow
25536
+ * does not. Additionally, using non-zero valuemin/valuemax values can cause
25537
+ * WebKit to incorrectly announce numeric valuetext values (such as a year
25538
+ * like "2024") as percentages: https://bugs.webkit.org/show_bug.cgi?id=273126
25539
+ */
25540
+ return (hAsync("div", { ref: (el) => (this.assistiveFocusable = el), class: "assistive-focusable", role: "slider", tabindex: this.disabled ? undefined : 0, "aria-label": this.ariaLabel, "aria-valuemin": 0, "aria-valuemax": 0, "aria-valuenow": 0, "aria-valuetext": valueText, "aria-orientation": "vertical", onKeyDown: (ev) => this.onKeyDown(ev) }));
25541
+ };
25413
25542
  }
25414
25543
  ariaLabelChanged(newValue) {
25415
25544
  this.ariaLabel = newValue;
@@ -25550,14 +25679,33 @@ class PickerColumn {
25550
25679
  render() {
25551
25680
  const { color, disabled, isActive, numericInput } = this;
25552
25681
  const mode = getIonMode$1(this);
25553
- return (hAsync(Host, { key: 'db903fd415f8a2d91994dececca481c1af8ba6a9', class: createColorClasses$1(color, {
25682
+ return (hAsync(Host, { key: 'ea0280355b2f87895bf7dddd289ccf473aa759f3', class: createColorClasses$1(color, {
25554
25683
  [mode]: true,
25555
25684
  ['picker-column-active']: isActive,
25556
25685
  ['picker-column-numeric-input']: numericInput,
25557
25686
  ['picker-column-disabled']: disabled,
25558
- }) }, hAsync("slot", { key: '02ce9e1dd7df91afcd50b06416552bcffb5dec98', name: "prefix" }), hAsync("div", { key: '6dfd7d2429bec19244a6b1afb4448121963a031b', class: "picker-opts", ref: (el) => {
25687
+ }) }, this.renderAssistiveFocusable(), hAsync("slot", { key: '482992131cdeb85b1f61430d7fe1322a16345769', name: "prefix" }), hAsync("div", { key: '43f7f80d621d411ef366b3ca1396299e8c9a0c97', "aria-hidden": "true", class: "picker-opts", ref: (el) => {
25559
25688
  this.scrollEl = el;
25560
- }, role: "slider", tabindex: this.disabled ? undefined : 0, "aria-label": this.ariaLabel, "aria-valuemin": 0, "aria-valuemax": 0, "aria-valuenow": 0, "aria-valuetext": this.getOptionValueText(this.activeItem), "aria-orientation": "vertical", onKeyDown: (ev) => this.onKeyDown(ev) }, hAsync("div", { key: 'e30ce0b9cefbfe4d4441fa33acf595da31855c3f', class: "picker-item-empty", "aria-hidden": "true" }, "\u00A0"), hAsync("div", { key: '8be2bd293c12c6ba720d9b31d0d561a96f42e97d', class: "picker-item-empty", "aria-hidden": "true" }, "\u00A0"), hAsync("div", { key: '8afdcddddabbf646fbb55cb0ba4448309a2c1dd9', class: "picker-item-empty", "aria-hidden": "true" }, "\u00A0"), hAsync("slot", { key: '6aa0dacc34d6848575ad5b122b9046982308ca43' }), hAsync("div", { key: '92ec8a357414c1b779b11d1dd18fb87a7ee63982', class: "picker-item-empty", "aria-hidden": "true" }, "\u00A0"), hAsync("div", { key: 'b89457cb74b5907c25594ff6720ac54ca537e933', class: "picker-item-empty", "aria-hidden": "true" }, "\u00A0"), hAsync("div", { key: '5bbc92e6bc24de08e39873bf08c5b668373ac0f8', class: "picker-item-empty", "aria-hidden": "true" }, "\u00A0")), hAsync("slot", { key: 'd7bf2b519214f0f3576a4ca79844ad97827dd97f', name: "suffix" })));
25689
+ },
25690
+ /**
25691
+ * When an element has an overlay scroll style and
25692
+ * a fixed height, Firefox will focus the scrollable
25693
+ * container if the content exceeds the container's
25694
+ * dimensions.
25695
+ *
25696
+ * This causes keyboard navigation to focus to this
25697
+ * element instead of going to the next element in
25698
+ * the tab order.
25699
+ *
25700
+ * The desired behavior is for the user to be able to
25701
+ * focus the assistive focusable element and tab to
25702
+ * the next element in the tab order. Instead of tabbing
25703
+ * to this element.
25704
+ *
25705
+ * To prevent this, we set the tabIndex to -1. This
25706
+ * will match the behavior of the other browsers.
25707
+ */
25708
+ tabIndex: -1 }, hAsync("div", { key: '13a9ee686132af32240710730765de4c0003a9e8', class: "picker-item-empty", "aria-hidden": "true" }, "\u00A0"), hAsync("div", { key: 'dbccba4920833cfcebe9b0fc763458ec3053705a', class: "picker-item-empty", "aria-hidden": "true" }, "\u00A0"), hAsync("div", { key: '682b43f83a5ea2e46067457f3af118535e111edb', class: "picker-item-empty", "aria-hidden": "true" }, "\u00A0"), hAsync("slot", { key: 'd27e1e1dc0504b2f4627a29912a05bb91e8e413a' }), hAsync("div", { key: '61c948dbb9cf7469aed3018542bc0954211585ba', class: "picker-item-empty", "aria-hidden": "true" }, "\u00A0"), hAsync("div", { key: 'cf46c277fbee65e35ff44ce0d53ce12aa9cbf9db', class: "picker-item-empty", "aria-hidden": "true" }, "\u00A0"), hAsync("div", { key: 'bbc0e2d491d3f836ab849493ade2f7fa6ad9244e', class: "picker-item-empty", "aria-hidden": "true" }, "\u00A0")), hAsync("slot", { key: 'd25cbbe14b2914fe7b878d43b4e3f4a8c8177d24', name: "suffix" })));
25561
25709
  }
25562
25710
  get el() { return getElement(this); }
25563
25711
  static get watchers() { return {
@@ -25949,9 +26097,9 @@ const DECELERATION_FRICTION = 0.97;
25949
26097
  const MAX_PICKER_SPEED = 90;
25950
26098
  const TRANSITION_DURATION = 150;
25951
26099
 
25952
- const pickerColumnOptionIosCss = ".picker-column-option-button{padding-left:0;padding-right:0;padding-top:0;padding-bottom:0;margin-left:0;margin-right:0;margin-top:0;margin-bottom:0;width:100%;height:34px;border:0px;outline:none;background:transparent;color:inherit;font-family:var(--ion-font-family, inherit);font-size:inherit;line-height:34px;text-align:inherit;text-overflow:ellipsis;white-space:nowrap;cursor:pointer;overflow:hidden}:host(.option-disabled){opacity:0.4}:host(.option-disabled) .picker-column-option-button{cursor:default}";
26100
+ const pickerColumnOptionIosCss = "button{padding-left:0;padding-right:0;padding-top:0;padding-bottom:0;margin-left:0;margin-right:0;margin-top:0;margin-bottom:0;width:100%;height:34px;border:0px;outline:none;background:transparent;color:inherit;font-family:var(--ion-font-family, inherit);font-size:inherit;line-height:34px;text-align:inherit;text-overflow:ellipsis;white-space:nowrap;cursor:pointer;overflow:hidden}:host(.option-disabled){opacity:0.4}:host(.option-disabled) button{cursor:default}";
25953
26101
 
25954
- const pickerColumnOptionMdCss = ".picker-column-option-button{padding-left:0;padding-right:0;padding-top:0;padding-bottom:0;margin-left:0;margin-right:0;margin-top:0;margin-bottom:0;width:100%;height:34px;border:0px;outline:none;background:transparent;color:inherit;font-family:var(--ion-font-family, inherit);font-size:inherit;line-height:34px;text-align:inherit;text-overflow:ellipsis;white-space:nowrap;cursor:pointer;overflow:hidden}:host(.option-disabled){opacity:0.4}:host(.option-disabled) .picker-column-option-button{cursor:default}:host(.option-active){color:var(--ion-color-base)}";
26102
+ const pickerColumnOptionMdCss = "button{padding-left:0;padding-right:0;padding-top:0;padding-bottom:0;margin-left:0;margin-right:0;margin-top:0;margin-bottom:0;width:100%;height:34px;border:0px;outline:none;background:transparent;color:inherit;font-family:var(--ion-font-family, inherit);font-size:inherit;line-height:34px;text-align:inherit;text-overflow:ellipsis;white-space:nowrap;cursor:pointer;overflow:hidden}:host(.option-disabled){opacity:0.4}:host(.option-disabled) button{cursor:default}:host(.option-active){color:var(--ion-color-base)}";
25955
26103
 
25956
26104
  class PickerColumnOption {
25957
26105
  constructor(hostRef) {
@@ -26043,7 +26191,7 @@ class PickerColumnOption {
26043
26191
  return (hAsync(Host, { key: 'f816729941aabcb31ddfdce3ffe2e2139030d715', class: createColorClasses$1(color, {
26044
26192
  [mode]: true,
26045
26193
  ['option-disabled']: disabled,
26046
- }) }, hAsync("div", { key: 'd942de84fd14d7dc06b1e5cf4f7920d1dc3c6371', class: 'picker-column-option-button', role: "button", "aria-label": ariaLabel, onClick: () => this.onClick() }, hAsync("slot", { key: 'b0df5717b42209e649097209a01476e1a66f5c5c' }))));
26194
+ }) }, hAsync("button", { key: '48dff7833bb60fc8331cd353a0885e6affa683d1', tabindex: "-1", "aria-label": ariaLabel, disabled: disabled, onClick: () => this.onClick() }, hAsync("slot", { key: 'f9224d0e7b7aa6c05b29abfdcfe0f30ad6ee3141' }))));
26047
26195
  }
26048
26196
  get el() { return getElement(this); }
26049
26197
  static get watchers() { return {
@@ -29202,6 +29350,14 @@ class Refresher {
29202
29350
  this.beginRefresh();
29203
29351
  this.didRefresh = true;
29204
29352
  hapticImpact({ style: ImpactStyle.Light });
29353
+ /**
29354
+ * Clear focus from any active element to prevent scroll jumps
29355
+ * when the refresh completes
29356
+ */
29357
+ const activeElement = document.activeElement;
29358
+ if ((activeElement === null || activeElement === void 0 ? void 0 : activeElement.blur) !== undefined) {
29359
+ activeElement.blur();
29360
+ }
29205
29361
  /**
29206
29362
  * Translate the content element otherwise when pointer is removed
29207
29363
  * from screen the scroll content will bounce back over the refresher
@@ -29617,6 +29773,15 @@ class Refresher {
29617
29773
  this.state = 8 /* RefresherState.Refreshing */;
29618
29774
  // place the content in a hangout position while it thinks
29619
29775
  this.setCss(this.pullMin, this.snapbackDuration, true, '');
29776
+ /**
29777
+ * Clear focus from any active element to prevent the browser
29778
+ * from restoring focus and causing scroll jumps after refresh.
29779
+ * This ensures the view stays at the top after refresh completes.
29780
+ */
29781
+ const activeElement = document.activeElement;
29782
+ if ((activeElement === null || activeElement === void 0 ? void 0 : activeElement.blur) !== undefined) {
29783
+ activeElement.blur();
29784
+ }
29620
29785
  // emit "refresh" because it was pulled down far enough
29621
29786
  // and they let go to begin refreshing
29622
29787
  this.ionRefresh.emit({
@@ -29699,7 +29864,7 @@ class Refresher {
29699
29864
  }
29700
29865
  render() {
29701
29866
  const mode = getIonMode$1(this);
29702
- return (hAsync(Host, { key: '8c7a5cc32da02a9cbeaa954258148683f60a6d1b', slot: "fixed", class: {
29867
+ return (hAsync(Host, { key: '2d1bd880877b698604542ab2d602d38b9504d975', slot: "fixed", class: {
29703
29868
  [mode]: true,
29704
29869
  // Used internally for styling
29705
29870
  [`refresher-${mode}`]: true,
@@ -34746,6 +34911,10 @@ class Textarea {
34746
34911
  * is applied in both cases.
34747
34912
  */
34748
34913
  this.hasFocus = false;
34914
+ /**
34915
+ * Track validation state for proper aria-live announcements
34916
+ */
34917
+ this.isInvalid = false;
34749
34918
  /**
34750
34919
  * Indicates whether and how the text value should be automatically capitalized as it is entered/edited by the user.
34751
34920
  * Available options: `"off"`, `"none"`, `"on"`, `"sentences"`, `"words"`, `"characters"`.
@@ -34891,10 +35060,18 @@ class Textarea {
34891
35060
  this.el.click();
34892
35061
  }
34893
35062
  }
35063
+ /**
35064
+ * Checks if the textarea is in an invalid state based on validation classes
35065
+ */
35066
+ checkValidationState() {
35067
+ return this.el.classList.contains('ion-touched') && this.el.classList.contains('ion-invalid');
35068
+ }
34894
35069
  connectedCallback() {
34895
35070
  const { el } = this;
34896
35071
  this.slotMutationController = createSlotMutationController(el, ['label', 'start', 'end'], () => forceUpdate());
34897
35072
  this.notchController = createNotchController(el, () => this.notchSpacerEl, () => this.labelSlot);
35073
+ // Always set initial state
35074
+ this.isInvalid = this.checkValidationState();
34898
35075
  this.debounceChanged();
34899
35076
  }
34900
35077
  disconnectedCallback() {
@@ -34906,6 +35083,11 @@ class Textarea {
34906
35083
  this.notchController.destroy();
34907
35084
  this.notchController = undefined;
34908
35085
  }
35086
+ // Clean up validation observer to prevent memory leaks
35087
+ if (this.validationObserver) {
35088
+ this.validationObserver.disconnect();
35089
+ this.validationObserver = undefined;
35090
+ }
34909
35091
  }
34910
35092
  componentWillLoad() {
34911
35093
  this.inheritedAttributes = Object.assign(Object.assign({}, inheritAriaAttributes(this.el)), inheritAttributes$1(this.el, ['data-form-type', 'title', 'tabindex', 'dir']));
@@ -35083,8 +35265,8 @@ class Textarea {
35083
35265
  ];
35084
35266
  }
35085
35267
  getHintTextID() {
35086
- const { el, helperText, errorText, helperTextId, errorTextId } = this;
35087
- if (el.classList.contains('ion-touched') && el.classList.contains('ion-invalid') && errorText) {
35268
+ const { isInvalid, helperText, errorText, helperTextId, errorTextId } = this;
35269
+ if (isInvalid && errorText) {
35088
35270
  return errorTextId;
35089
35271
  }
35090
35272
  if (helperText) {
@@ -35143,7 +35325,7 @@ class Textarea {
35143
35325
  * TODO(FW-5592): Remove hasStartEndSlots condition
35144
35326
  */
35145
35327
  const labelShouldFloat = labelPlacement === 'stacked' || (labelPlacement === 'floating' && (hasValue || hasFocus || hasStartEndSlots));
35146
- return (hAsync(Host, { key: 'd9f2ede0107987fc42c99e310cd2336bad5a5755', class: createColorClasses$1(this.color, {
35328
+ return (hAsync(Host, { key: 'de258335fde7fa934cf3bd58c469525440457468', class: createColorClasses$1(this.color, {
35147
35329
  [mode]: true,
35148
35330
  'has-value': hasValue,
35149
35331
  'has-focus': hasFocus,
@@ -35152,7 +35334,7 @@ class Textarea {
35152
35334
  [`textarea-shape-${shape}`]: shape !== undefined,
35153
35335
  [`textarea-label-placement-${labelPlacement}`]: true,
35154
35336
  'textarea-disabled': disabled,
35155
- }) }, hAsync("label", { key: '9de598b95237462bb3bccffaefe83afbb43554b8', class: "textarea-wrapper", htmlFor: inputId, onClick: this.onLabelClick }, this.renderLabelContainer(), hAsync("div", { key: 'e33c426c6541d723ccc246bb404c03687726ff83', class: "textarea-wrapper-inner" }, hAsync("div", { key: '521e11af9d54d281b0a2b1c25bcfc6f742c18296', class: "start-slot-wrapper" }, hAsync("slot", { key: '515523f6ca3ce0e5dd08f3275c21a190fb1ca177', name: "start" })), hAsync("div", { key: '916e01e00de8400ae00ef06bc1fb62d8be2eee08', class: "native-wrapper", ref: (el) => (this.textareaWrapper = el) }, hAsync("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)), hAsync("div", { key: '80aca9ea9546dca9d38efd291a6b0be384bb6978', class: "end-slot-wrapper" }, hAsync("slot", { key: '407fab16c66a9f4a542369bfecc0d9afa0065977', name: "end" }))), shouldRenderHighlight && hAsync("div", { key: 'f00523a6698fac8a1996e04303487bef01d10f25', class: "textarea-highlight" })), this.renderBottomContent()));
35337
+ }) }, hAsync("label", { key: '79fc87122871d45316b1c5fd0a467bf85351e804', class: "textarea-wrapper", htmlFor: inputId, onClick: this.onLabelClick }, this.renderLabelContainer(), hAsync("div", { key: '2375888a47557b41ea3b3bf8eae1fd3f2a4032bd', class: "textarea-wrapper-inner" }, hAsync("div", { key: 'c5af683527a870bd85256b6de8a7d216283403dd', class: "start-slot-wrapper" }, hAsync("slot", { key: 'd972e5784403c5cc6ed067ae578ce23ff8303e1e', name: "start" })), hAsync("div", { key: '1092067609d912a76a8679767611e4ca3e071c6f', class: "native-wrapper", ref: (el) => (this.textareaWrapper = el) }, hAsync("textarea", Object.assign({ key: 'cd23a4bad4d5b460830340009d24d0618f14fd73', 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: 'c91a5456f4490ef8699f2e44b4a6c64f6084f5fd', class: "end-slot-wrapper" }, hAsync("slot", { key: 'b71ba7ba0d74091e81784432dcffa832b66f3f25', name: "end" }))), shouldRenderHighlight && hAsync("div", { key: '757c3287e1cea5ee851227407685a437f8408c54', class: "textarea-highlight" })), this.renderBottomContent()));
35156
35338
  }
35157
35339
  get el() { return getElement(this); }
35158
35340
  static get watchers() { return {
@@ -35197,6 +35379,7 @@ class Textarea {
35197
35379
  "labelPlacement": [1, "label-placement"],
35198
35380
  "shape": [1],
35199
35381
  "hasFocus": [32],
35382
+ "isInvalid": [32],
35200
35383
  "setFocus": [64],
35201
35384
  "getInputElement": [64]
35202
35385
  },