@ionic/core 8.7.4-dev.11755809082.11a7702b → 8.7.4-dev.11756388042.1a103a79

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 (84) hide show
  1. package/components/ion-datetime.js +4 -3
  2. package/components/ion-input.js +8 -63
  3. package/components/ion-menu.js +5 -2
  4. package/components/ion-textarea.js +7 -62
  5. package/components/overlays.js +18 -109
  6. package/components/picker-column-option.js +3 -3
  7. package/components/picker-column.js +3 -39
  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 +8 -62
  13. package/dist/cjs/ion-loading.cjs.entry.js +1 -1
  14. package/dist/cjs/ion-menu_3.cjs.entry.js +6 -3
  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 +3 -39
  18. package/dist/cjs/ion-popover.cjs.entry.js +1 -1
  19. package/dist/cjs/ion-select-modal.cjs.entry.js +1 -1
  20. package/dist/cjs/ion-select_3.cjs.entry.js +1 -1
  21. package/dist/cjs/ion-textarea.cjs.entry.js +7 -61
  22. package/dist/cjs/ion-toast.cjs.entry.js +1 -1
  23. package/dist/cjs/ionic.cjs.js +1 -1
  24. package/dist/cjs/loader.cjs.js +1 -1
  25. package/dist/cjs/{overlays-CglR7j-u.js → overlays-v_jc1Hok.js} +17 -108
  26. package/dist/collection/components/datetime/datetime.js +4 -3
  27. package/dist/collection/components/input/input.js +9 -64
  28. package/dist/collection/components/menu/menu.js +9 -2
  29. package/dist/collection/components/picker-column/picker-column.js +3 -39
  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/textarea/textarea.js +8 -63
  34. package/dist/collection/utils/overlays.js +17 -109
  35. package/dist/docs.json +5 -1
  36. package/dist/esm/index.js +1 -1
  37. package/dist/esm/ion-action-sheet.entry.js +1 -1
  38. package/dist/esm/ion-alert.entry.js +1 -1
  39. package/dist/esm/ion-datetime_3.entry.js +4 -4
  40. package/dist/esm/ion-input.entry.js +8 -62
  41. package/dist/esm/ion-loading.entry.js +1 -1
  42. package/dist/esm/ion-menu_3.entry.js +6 -3
  43. package/dist/esm/ion-modal.entry.js +1 -1
  44. package/dist/esm/ion-picker-column-option.entry.js +3 -3
  45. package/dist/esm/ion-picker-column.entry.js +3 -39
  46. package/dist/esm/ion-popover.entry.js +1 -1
  47. package/dist/esm/ion-select-modal.entry.js +1 -1
  48. package/dist/esm/ion-select_3.entry.js +1 -1
  49. package/dist/esm/ion-textarea.entry.js +7 -61
  50. package/dist/esm/ion-toast.entry.js +1 -1
  51. package/dist/esm/ionic.js +1 -1
  52. package/dist/esm/loader.js +1 -1
  53. package/dist/esm/{overlays-ZX_4-t_r.js → overlays-BJaRj3Rj.js} +18 -109
  54. package/dist/ionic/index.esm.js +1 -1
  55. package/dist/ionic/ionic.esm.js +1 -1
  56. package/dist/ionic/{p-f2884bc2.entry.js → p-095073c6.entry.js} +1 -1
  57. package/dist/ionic/p-1488b7cc.entry.js +4 -0
  58. package/dist/ionic/p-1ab0fc21.entry.js +4 -0
  59. package/dist/ionic/{p-92e8f208.entry.js → p-2060feb1.entry.js} +1 -1
  60. package/dist/ionic/p-22a0f820.entry.js +4 -0
  61. package/dist/ionic/{p-c575e7ce.entry.js → p-5a9be5a3.entry.js} +1 -1
  62. package/dist/ionic/{p-7bac2c5f.entry.js → p-60ff6bf6.entry.js} +1 -1
  63. package/dist/ionic/p-8888efe4.entry.js +4 -0
  64. package/dist/ionic/{p-ab33ef20.entry.js → p-89c2e0a3.entry.js} +1 -1
  65. package/dist/ionic/p-CoDfkBuk.js +4 -0
  66. package/dist/ionic/{p-91d6ccb0.entry.js → p-b17f0554.entry.js} +1 -1
  67. package/dist/ionic/p-b292804d.entry.js +4 -0
  68. package/dist/ionic/p-c5210d3e.entry.js +4 -0
  69. package/dist/ionic/{p-ac434970.entry.js → p-cd93c119.entry.js} +1 -1
  70. package/dist/ionic/p-dc8d0bb9.entry.js +4 -0
  71. package/dist/types/components/input/input.d.ts +0 -9
  72. package/dist/types/components/picker-column/picker-column.d.ts +0 -7
  73. package/dist/types/components/textarea/textarea.d.ts +0 -9
  74. package/hydrate/index.js +46 -248
  75. package/hydrate/index.mjs +46 -248
  76. package/package.json +2 -2
  77. package/dist/ionic/p-1d5b934a.entry.js +0 -4
  78. package/dist/ionic/p-1d65c601.entry.js +0 -4
  79. package/dist/ionic/p-29032e49.entry.js +0 -4
  80. package/dist/ionic/p-33017191.entry.js +0 -4
  81. package/dist/ionic/p-8d96a0cd.entry.js +0 -4
  82. package/dist/ionic/p-982fe1c4.entry.js +0 -4
  83. package/dist/ionic/p-CSwZyt05.js +0 -4
  84. package/dist/ionic/p-f456d176.entry.js +0 -4
@@ -5,6 +5,7 @@ import { Host, h, writeTask } from "@stencil/core";
5
5
  import { startFocusVisible } from "../../utils/focus-visible";
6
6
  import { getElementRoot, raf, renderHiddenInput } from "../../utils/helpers";
7
7
  import { printIonError, printIonWarning } from "../../utils/logging/index";
8
+ import { FOCUS_TRAP_DISABLE_CLASS } from "../../utils/overlays";
8
9
  import { isRTL } from "../../utils/rtl/index";
9
10
  import { createColorClasses } from "../../utils/theme";
10
11
  import { caretDownSharp, caretUpSharp, chevronBack, chevronDown, chevronForward } from "ionicons/icons";
@@ -1099,7 +1100,7 @@ export class Datetime {
1099
1100
  const renderArray = forcePresentation === 'time-date'
1100
1101
  ? [this.renderTimePickerColumns(forcePresentation), this.renderDatePickerColumns(forcePresentation)]
1101
1102
  : [this.renderDatePickerColumns(forcePresentation), this.renderTimePickerColumns(forcePresentation)];
1102
- return h("ion-picker", null, renderArray);
1103
+ return h("ion-picker", { class: FOCUS_TRAP_DISABLE_CLASS }, renderArray);
1103
1104
  }
1104
1105
  renderDatePickerColumns(forcePresentation) {
1105
1106
  return forcePresentation === 'date-time' || forcePresentation === 'time-date'
@@ -1656,7 +1657,7 @@ export class Datetime {
1656
1657
  const hasDatePresentation = presentation === 'date' || presentation === 'date-time' || presentation === 'time-date';
1657
1658
  const hasWheelVariant = hasDatePresentation && preferWheel;
1658
1659
  renderHiddenInput(true, el, name, formatValue(value), disabled);
1659
- return (h(Host, { key: 'f35cf200ff05d33074576e3d2754d3b2a0735b96', "aria-disabled": disabled ? 'true' : null, onFocus: this.onFocus, onBlur: this.onBlur, class: Object.assign({}, createColorClasses(color, {
1660
+ return (h(Host, { key: '57492534800ea059a7c2bbd9f0059cc0b75ae8d2', "aria-disabled": disabled ? 'true' : null, onFocus: this.onFocus, onBlur: this.onBlur, class: Object.assign({}, createColorClasses(color, {
1660
1661
  [mode]: true,
1661
1662
  ['datetime-readonly']: readonly,
1662
1663
  ['datetime-disabled']: disabled,
@@ -1666,7 +1667,7 @@ export class Datetime {
1666
1667
  [`datetime-size-${size}`]: true,
1667
1668
  [`datetime-prefer-wheel`]: hasWheelVariant,
1668
1669
  [`datetime-grid`]: isGridStyle,
1669
- })) }, h("div", { key: '859e9354a12bfa58ac3f964c2e66839f17071c00', class: "intersection-tracker", ref: (el) => (this.intersectionTrackerRef = el) }), this.renderDatetime(mode)));
1670
+ })) }, h("div", { key: '97dac5e5195635ac0bc5fb472b9d09e5c3c6bbc3', class: "intersection-tracker", ref: (el) => (this.intersectionTrackerRef = el) }), this.renderDatetime(mode)));
1670
1671
  }
1671
1672
  static get is() { return "ion-datetime"; }
1672
1673
  static get encapsulation() { return "shadow"; }
@@ -39,10 +39,6 @@ export class Input {
39
39
  * is applied in both cases.
40
40
  */
41
41
  this.hasFocus = false;
42
- /**
43
- * Track validation state for proper aria-live announcements
44
- */
45
- this.isInvalid = false;
46
42
  /**
47
43
  * Indicates whether and how the text value should be automatically capitalized as it is entered/edited by the user.
48
44
  * Available options: `"off"`, `"none"`, `"on"`, `"sentences"`, `"words"`, `"characters"`.
@@ -128,19 +124,6 @@ export class Input {
128
124
  }
129
125
  this.didInputClearOnEdit = false;
130
126
  this.ionBlur.emit(ev);
131
- /**
132
- * Check validation state after blur to handle framework-managed classes.
133
- * Frameworks like Angular update classes asynchronously, often using
134
- * requestAnimationFrame or promises. Using setTimeout ensures we check
135
- * after all microtasks and animation frames have completed.
136
- */
137
- setTimeout(() => {
138
- const newIsInvalid = this.checkValidationState();
139
- if (this.isInvalid !== newIsInvalid) {
140
- this.isInvalid = newIsInvalid;
141
- forceUpdate(this);
142
- }
143
- }, 100);
144
127
  };
145
128
  this.onFocus = (ev) => {
146
129
  this.hasFocus = true;
@@ -240,42 +223,10 @@ export class Input {
240
223
  componentWillLoad() {
241
224
  this.inheritedAttributes = Object.assign(Object.assign({}, inheritAriaAttributes(this.el)), inheritAttributes(this.el, ['tabindex', 'title', 'data-form-type', 'dir']));
242
225
  }
243
- /**
244
- * Checks if the input is in an invalid state based on validation classes
245
- */
246
- checkValidationState() {
247
- // Check for both Ionic and Angular validation classes on the element itself
248
- // Angular applies ng-touched/ng-invalid directly to the host element with ngModel
249
- const hasIonTouched = this.el.classList.contains('ion-touched');
250
- const hasIonInvalid = this.el.classList.contains('ion-invalid');
251
- const hasNgTouched = this.el.classList.contains('ng-touched');
252
- const hasNgInvalid = this.el.classList.contains('ng-invalid');
253
- // Return true if we have both touched and invalid states from either framework
254
- const isTouched = hasIonTouched || hasNgTouched;
255
- const isInvalid = hasIonInvalid || hasNgInvalid;
256
- return isTouched && isInvalid;
257
- }
258
226
  connectedCallback() {
259
227
  const { el } = this;
260
228
  this.slotMutationController = createSlotMutationController(el, ['label', 'start', 'end'], () => forceUpdate(this));
261
229
  this.notchController = createNotchController(el, () => this.notchSpacerEl, () => this.labelSlot);
262
- // Watch for class changes to update validation state
263
- if (Build.isBrowser && typeof MutationObserver !== 'undefined') {
264
- this.validationObserver = new MutationObserver(() => {
265
- const newIsInvalid = this.checkValidationState();
266
- if (this.isInvalid !== newIsInvalid) {
267
- this.isInvalid = newIsInvalid;
268
- // Force a re-render to update aria-describedby immediately
269
- forceUpdate(this);
270
- }
271
- });
272
- this.validationObserver.observe(el, {
273
- attributes: true,
274
- attributeFilter: ['class'],
275
- });
276
- }
277
- // Always set initial state
278
- this.isInvalid = this.checkValidationState();
279
230
  this.debounceChanged();
280
231
  if (Build.isBrowser) {
281
232
  document.dispatchEvent(new CustomEvent('ionInputDidLoad', {
@@ -312,11 +263,6 @@ export class Input {
312
263
  this.notchController.destroy();
313
264
  this.notchController = undefined;
314
265
  }
315
- // Clean up validation observer to prevent memory leaks
316
- if (this.validationObserver) {
317
- this.validationObserver.disconnect();
318
- this.validationObserver = undefined;
319
- }
320
266
  }
321
267
  /**
322
268
  * Sets focus on the native `input` in `ion-input`. Use this method instead of the global
@@ -418,15 +364,15 @@ export class Input {
418
364
  * Renders the helper text or error text values
419
365
  */
420
366
  renderHintText() {
421
- const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
367
+ const { helperText, errorText, helperTextId, errorTextId } = this;
422
368
  return [
423
- helperText && (h("div", { id: helperTextId, class: "helper-text", "aria-live": "polite" }, helperText)),
424
- errorText && (h("div", { id: errorTextId, class: "error-text", "aria-live": "assertive", "aria-atomic": "true", role: "alert", style: { display: isInvalid ? 'block' : 'none' } }, errorText)),
369
+ h("div", { id: helperTextId, class: "helper-text" }, helperText),
370
+ h("div", { id: errorTextId, class: "error-text" }, errorText),
425
371
  ];
426
372
  }
427
373
  getHintTextID() {
428
- const { isInvalid, helperText, errorText, helperTextId, errorTextId } = this;
429
- if (isInvalid && errorText) {
374
+ const { el, helperText, errorText, helperTextId, errorTextId } = this;
375
+ if (el.classList.contains('ion-touched') && el.classList.contains('ion-invalid') && errorText) {
430
376
  return errorTextId;
431
377
  }
432
378
  if (helperText) {
@@ -539,7 +485,7 @@ export class Input {
539
485
  * TODO(FW-5592): Remove hasStartEndSlots condition
540
486
  */
541
487
  const labelShouldFloat = labelPlacement === 'stacked' || (labelPlacement === 'floating' && (hasValue || hasFocus || hasStartEndSlots));
542
- return (h(Host, { key: '1a141906096fc637d7de923edf6a9ea3a0168f5f', class: createColorClasses(this.color, {
488
+ return (h(Host, { key: '41b2526627e7d2773a80f011b123284203a71ca0', class: createColorClasses(this.color, {
543
489
  [mode]: true,
544
490
  'has-value': hasValue,
545
491
  'has-focus': hasFocus,
@@ -550,14 +496,14 @@ export class Input {
550
496
  'in-item': inItem,
551
497
  'in-item-color': hostContext('ion-item.ion-color', this.el),
552
498
  'input-disabled': disabled,
553
- }) }, 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) => {
499
+ }) }, 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) => {
554
500
  /**
555
501
  * This prevents mobile browsers from
556
502
  * blurring the input when the clear
557
503
  * button is activated.
558
504
  */
559
505
  ev.preventDefault();
560
- }, 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()));
506
+ }, 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()));
561
507
  }
562
508
  static get is() { return "ion-input"; }
563
509
  static get encapsulation() { return "scoped"; }
@@ -1256,8 +1202,7 @@ export class Input {
1256
1202
  }
1257
1203
  static get states() {
1258
1204
  return {
1259
- "hasFocus": {},
1260
- "isInvalid": {}
1205
+ "hasFocus": {}
1261
1206
  };
1262
1207
  }
1263
1208
  static get events() {
@@ -293,6 +293,9 @@ export class Menu {
293
293
  * @param role The role of the element that is closing the menu.
294
294
  */
295
295
  setOpen(shouldOpen, animated = true, role) {
296
+ var _a;
297
+ // Blur the active element to prevent it from being kept focused inside an element that will be set with aria-hidden="true"
298
+ (_a = document.activeElement) === null || _a === void 0 ? void 0 : _a.blur();
296
299
  return menuController._setOpen(this, shouldOpen, animated, role);
297
300
  }
298
301
  trapKeyboardFocus(ev, doc) {
@@ -655,14 +658,14 @@ export class Menu {
655
658
  * the ionBackButton listener in the menu controller
656
659
  * will handle closing the menu when Escape is pressed.
657
660
  */
658
- return (h(Host, { key: 'a5c75aa40a34530b56ee3b98d706a5ac5ae300de', onKeyDown: shouldUseCloseWatcher() ? null : this.onKeydown, role: "navigation", "aria-label": inheritedAttributes['aria-label'] || 'menu', class: {
661
+ return (h(Host, { key: '70a427f3414a476414c3386efe6c8723fd37eccf', onKeyDown: shouldUseCloseWatcher() ? null : this.onKeydown, role: "navigation", "aria-label": inheritedAttributes['aria-label'] || 'menu', class: {
659
662
  [mode]: true,
660
663
  [`menu-type-${type}`]: true,
661
664
  'menu-enabled': !disabled,
662
665
  [`menu-side-${side}`]: true,
663
666
  'menu-pane-visible': isPaneVisible,
664
667
  'split-pane-side': hostContext('ion-split-pane', el),
665
- } }, h("div", { key: '3f5f70acd4d3ed6bb445122f4f01d73db738a75f', class: "menu-inner", part: "container", ref: (el) => (this.menuInnerEl = el) }, h("slot", { key: '3161326c9330e7f7441299c428b87a91b31a83e9' })), h("ion-backdrop", { key: '917b50f38489bdf03d0c642af8b4e4e172c7dc4c', ref: (el) => (this.backdropEl = el), class: "menu-backdrop", tappable: false, stopPropagation: false, part: "backdrop" })));
668
+ } }, h("div", { key: '83af04e5a47d5a92caafaf06088a7114ae61984b', class: "menu-inner", part: "container", ref: (el) => (this.menuInnerEl = el) }, h("slot", { key: '7b35048642864bd0f30de9f6b61c949c1b601692' })), h("ion-backdrop", { key: '347af516c7970d80dd11c6d1ed61e9a040ceb5fb', ref: (el) => (this.backdropEl = el), class: "menu-backdrop", tappable: false, stopPropagation: false, part: "backdrop" })));
666
669
  }
667
670
  static get is() { return "ion-menu"; }
668
671
  static get encapsulation() { return "shadow"; }
@@ -1071,6 +1074,10 @@ export class Menu {
1071
1074
  "Promise": {
1072
1075
  "location": "global",
1073
1076
  "id": "global::Promise"
1077
+ },
1078
+ "HTMLElement": {
1079
+ "location": "global",
1080
+ "id": "global::HTMLElement"
1074
1081
  }
1075
1082
  },
1076
1083
  "return": "Promise<boolean>"
@@ -411,23 +411,6 @@ export class PickerColumn {
411
411
  var _a;
412
412
  return el ? (_a = el.getAttribute('aria-label')) !== null && _a !== void 0 ? _a : el.innerText : '';
413
413
  };
414
- /**
415
- * Render an element that overlays the column. This element is for assistive
416
- * tech to allow users to navigate the column up/down. This element should receive
417
- * focus as it listens for synthesized keyboard events as required by the
418
- * slider role: https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/slider_role
419
- */
420
- this.renderAssistiveFocusable = () => {
421
- const { activeItem } = this;
422
- const valueText = this.getOptionValueText(activeItem);
423
- /**
424
- * When using the picker, the valuetext provides important context that valuenow
425
- * does not. Additionally, using non-zero valuemin/valuemax values can cause
426
- * WebKit to incorrectly announce numeric valuetext values (such as a year
427
- * like "2024") as percentages: https://bugs.webkit.org/show_bug.cgi?id=273126
428
- */
429
- return (h("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) }));
430
- };
431
414
  }
432
415
  ariaLabelChanged(newValue) {
433
416
  this.ariaLabel = newValue;
@@ -568,33 +551,14 @@ export class PickerColumn {
568
551
  render() {
569
552
  const { color, disabled, isActive, numericInput } = this;
570
553
  const mode = getIonMode(this);
571
- return (h(Host, { key: 'ea0280355b2f87895bf7dddd289ccf473aa759f3', class: createColorClasses(color, {
554
+ return (h(Host, { key: 'db903fd415f8a2d91994dececca481c1af8ba6a9', class: createColorClasses(color, {
572
555
  [mode]: true,
573
556
  ['picker-column-active']: isActive,
574
557
  ['picker-column-numeric-input']: numericInput,
575
558
  ['picker-column-disabled']: disabled,
576
- }) }, this.renderAssistiveFocusable(), h("slot", { key: '482992131cdeb85b1f61430d7fe1322a16345769', name: "prefix" }), h("div", { key: '43f7f80d621d411ef366b3ca1396299e8c9a0c97', "aria-hidden": "true", class: "picker-opts", ref: (el) => {
559
+ }) }, h("slot", { key: '02ce9e1dd7df91afcd50b06416552bcffb5dec98', name: "prefix" }), h("div", { key: '6dfd7d2429bec19244a6b1afb4448121963a031b', class: "picker-opts", ref: (el) => {
577
560
  this.scrollEl = el;
578
- },
579
- /**
580
- * When an element has an overlay scroll style and
581
- * a fixed height, Firefox will focus the scrollable
582
- * container if the content exceeds the container's
583
- * dimensions.
584
- *
585
- * This causes keyboard navigation to focus to this
586
- * element instead of going to the next element in
587
- * the tab order.
588
- *
589
- * The desired behavior is for the user to be able to
590
- * focus the assistive focusable element and tab to
591
- * the next element in the tab order. Instead of tabbing
592
- * to this element.
593
- *
594
- * To prevent this, we set the tabIndex to -1. This
595
- * will match the behavior of the other browsers.
596
- */
597
- tabIndex: -1 }, h("div", { key: '13a9ee686132af32240710730765de4c0003a9e8', class: "picker-item-empty", "aria-hidden": "true" }, "\u00A0"), h("div", { key: 'dbccba4920833cfcebe9b0fc763458ec3053705a', class: "picker-item-empty", "aria-hidden": "true" }, "\u00A0"), h("div", { key: '682b43f83a5ea2e46067457f3af118535e111edb', class: "picker-item-empty", "aria-hidden": "true" }, "\u00A0"), h("slot", { key: 'd27e1e1dc0504b2f4627a29912a05bb91e8e413a' }), h("div", { key: '61c948dbb9cf7469aed3018542bc0954211585ba', class: "picker-item-empty", "aria-hidden": "true" }, "\u00A0"), h("div", { key: 'cf46c277fbee65e35ff44ce0d53ce12aa9cbf9db', class: "picker-item-empty", "aria-hidden": "true" }, "\u00A0"), h("div", { key: 'bbc0e2d491d3f836ab849493ade2f7fa6ad9244e', class: "picker-item-empty", "aria-hidden": "true" }, "\u00A0")), h("slot", { key: 'd25cbbe14b2914fe7b878d43b4e3f4a8c8177d24', name: "suffix" })));
561
+ }, 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) }, h("div", { key: 'e30ce0b9cefbfe4d4441fa33acf595da31855c3f', class: "picker-item-empty", "aria-hidden": "true" }, "\u00A0"), h("div", { key: '8be2bd293c12c6ba720d9b31d0d561a96f42e97d', class: "picker-item-empty", "aria-hidden": "true" }, "\u00A0"), h("div", { key: '8afdcddddabbf646fbb55cb0ba4448309a2c1dd9', class: "picker-item-empty", "aria-hidden": "true" }, "\u00A0"), h("slot", { key: '6aa0dacc34d6848575ad5b122b9046982308ca43' }), h("div", { key: '92ec8a357414c1b779b11d1dd18fb87a7ee63982', class: "picker-item-empty", "aria-hidden": "true" }, "\u00A0"), h("div", { key: 'b89457cb74b5907c25594ff6720ac54ca537e933', class: "picker-item-empty", "aria-hidden": "true" }, "\u00A0"), h("div", { key: '5bbc92e6bc24de08e39873bf08c5b668373ac0f8', class: "picker-item-empty", "aria-hidden": "true" }, "\u00A0")), h("slot", { key: 'd7bf2b519214f0f3576a4ca79844ad97827dd97f', name: "suffix" })));
598
562
  }
599
563
  static get is() { return "ion-picker-column"; }
600
564
  static get encapsulation() { return "shadow"; }
@@ -53,7 +53,7 @@
53
53
  * }
54
54
  * }
55
55
  */
56
- button {
56
+ .picker-column-option-button {
57
57
  padding-left: 0;
58
58
  padding-right: 0;
59
59
  padding-top: 0;
@@ -82,6 +82,6 @@ button {
82
82
  opacity: 0.4;
83
83
  }
84
84
 
85
- :host(.option-disabled) button {
85
+ :host(.option-disabled) .picker-column-option-button {
86
86
  cursor: default;
87
87
  }
@@ -94,7 +94,7 @@ export class PickerColumnOption {
94
94
  return (h(Host, { key: 'f816729941aabcb31ddfdce3ffe2e2139030d715', class: createColorClasses(color, {
95
95
  [mode]: true,
96
96
  ['option-disabled']: disabled,
97
- }) }, h("button", { key: '48dff7833bb60fc8331cd353a0885e6affa683d1', tabindex: "-1", "aria-label": ariaLabel, disabled: disabled, onClick: () => this.onClick() }, h("slot", { key: 'f9224d0e7b7aa6c05b29abfdcfe0f30ad6ee3141' }))));
97
+ }) }, h("div", { key: 'd942de84fd14d7dc06b1e5cf4f7920d1dc3c6371', class: 'picker-column-option-button', role: "button", "aria-label": ariaLabel, onClick: () => this.onClick() }, h("slot", { key: 'b0df5717b42209e649097209a01476e1a66f5c5c' }))));
98
98
  }
99
99
  static get is() { return "ion-picker-column-option"; }
100
100
  static get encapsulation() { return "shadow"; }
@@ -53,7 +53,7 @@
53
53
  * }
54
54
  * }
55
55
  */
56
- button {
56
+ .picker-column-option-button {
57
57
  padding-left: 0;
58
58
  padding-right: 0;
59
59
  padding-top: 0;
@@ -82,7 +82,7 @@ button {
82
82
  opacity: 0.4;
83
83
  }
84
84
 
85
- :host(.option-disabled) button {
85
+ :host(.option-disabled) .picker-column-option-button {
86
86
  cursor: default;
87
87
  }
88
88
 
@@ -37,10 +37,6 @@ export class Textarea {
37
37
  * is applied in both cases.
38
38
  */
39
39
  this.hasFocus = false;
40
- /**
41
- * Track validation state for proper aria-live announcements
42
- */
43
- this.isInvalid = false;
44
40
  /**
45
41
  * Indicates whether and how the text value should be automatically capitalized as it is entered/edited by the user.
46
42
  * Available options: `"off"`, `"none"`, `"on"`, `"sentences"`, `"words"`, `"characters"`.
@@ -130,19 +126,6 @@ export class Textarea {
130
126
  }
131
127
  this.didTextareaClearOnEdit = false;
132
128
  this.ionBlur.emit(ev);
133
- /**
134
- * Check validation state after blur to handle framework-managed classes.
135
- * Frameworks like Angular update classes asynchronously, often using
136
- * requestAnimationFrame or promises. Using setTimeout ensures we check
137
- * after all microtasks and animation frames have completed.
138
- */
139
- setTimeout(() => {
140
- const newIsInvalid = this.checkValidationState();
141
- if (this.isInvalid !== newIsInvalid) {
142
- this.isInvalid = newIsInvalid;
143
- forceUpdate(this);
144
- }
145
- }, 100);
146
129
  };
147
130
  this.onKeyDown = (ev) => {
148
131
  this.checkClearOnEdit(ev);
@@ -200,42 +183,10 @@ export class Textarea {
200
183
  this.el.click();
201
184
  }
202
185
  }
203
- /**
204
- * Checks if the textarea is in an invalid state based on validation classes
205
- */
206
- checkValidationState() {
207
- // Check for both Ionic and Angular validation classes on the element itself
208
- // Angular applies ng-touched/ng-invalid directly to the host element with ngModel
209
- const hasIonTouched = this.el.classList.contains('ion-touched');
210
- const hasIonInvalid = this.el.classList.contains('ion-invalid');
211
- const hasNgTouched = this.el.classList.contains('ng-touched');
212
- const hasNgInvalid = this.el.classList.contains('ng-invalid');
213
- // Return true if we have both touched and invalid states from either framework
214
- const isTouched = hasIonTouched || hasNgTouched;
215
- const isInvalid = hasIonInvalid || hasNgInvalid;
216
- return isTouched && isInvalid;
217
- }
218
186
  connectedCallback() {
219
187
  const { el } = this;
220
188
  this.slotMutationController = createSlotMutationController(el, ['label', 'start', 'end'], () => forceUpdate(this));
221
189
  this.notchController = createNotchController(el, () => this.notchSpacerEl, () => this.labelSlot);
222
- // Watch for class changes to update validation state
223
- if (Build.isBrowser && typeof MutationObserver !== 'undefined') {
224
- this.validationObserver = new MutationObserver(() => {
225
- const newIsInvalid = this.checkValidationState();
226
- if (this.isInvalid !== newIsInvalid) {
227
- this.isInvalid = newIsInvalid;
228
- // Force a re-render to update aria-describedby immediately
229
- forceUpdate(this);
230
- }
231
- });
232
- this.validationObserver.observe(el, {
233
- attributes: true,
234
- attributeFilter: ['class'],
235
- });
236
- }
237
- // Always set initial state
238
- this.isInvalid = this.checkValidationState();
239
190
  this.debounceChanged();
240
191
  if (Build.isBrowser) {
241
192
  document.dispatchEvent(new CustomEvent('ionInputDidLoad', {
@@ -257,11 +208,6 @@ export class Textarea {
257
208
  this.notchController.destroy();
258
209
  this.notchController = undefined;
259
210
  }
260
- // Clean up validation observer to prevent memory leaks
261
- if (this.validationObserver) {
262
- this.validationObserver.disconnect();
263
- this.validationObserver = undefined;
264
- }
265
211
  }
266
212
  componentWillLoad() {
267
213
  this.inheritedAttributes = Object.assign(Object.assign({}, inheritAriaAttributes(this.el)), inheritAttributes(this.el, ['data-form-type', 'title', 'tabindex', 'dir']));
@@ -432,15 +378,15 @@ export class Textarea {
432
378
  * Renders the helper text or error text values
433
379
  */
434
380
  renderHintText() {
435
- const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
381
+ const { helperText, errorText, helperTextId, errorTextId } = this;
436
382
  return [
437
- helperText && (h("div", { id: helperTextId, class: "helper-text", "aria-live": "polite" }, helperText)),
438
- errorText && (h("div", { id: errorTextId, class: "error-text", "aria-live": "assertive", "aria-atomic": "true", role: "alert", style: { display: isInvalid ? 'block' : 'none' } }, errorText)),
383
+ h("div", { id: helperTextId, class: "helper-text" }, helperText),
384
+ h("div", { id: errorTextId, class: "error-text" }, errorText),
439
385
  ];
440
386
  }
441
387
  getHintTextID() {
442
- const { isInvalid, helperText, errorText, helperTextId, errorTextId } = this;
443
- if (isInvalid && errorText) {
388
+ const { el, helperText, errorText, helperTextId, errorTextId } = this;
389
+ if (el.classList.contains('ion-touched') && el.classList.contains('ion-invalid') && errorText) {
444
390
  return errorTextId;
445
391
  }
446
392
  if (helperText) {
@@ -499,7 +445,7 @@ export class Textarea {
499
445
  * TODO(FW-5592): Remove hasStartEndSlots condition
500
446
  */
501
447
  const labelShouldFloat = labelPlacement === 'stacked' || (labelPlacement === 'floating' && (hasValue || hasFocus || hasStartEndSlots));
502
- return (h(Host, { key: '803c7648de15b8569c3df01692548018cc660510', class: createColorClasses(this.color, {
448
+ return (h(Host, { key: 'd9f2ede0107987fc42c99e310cd2336bad5a5755', class: createColorClasses(this.color, {
503
449
  [mode]: true,
504
450
  'has-value': hasValue,
505
451
  'has-focus': hasFocus,
@@ -508,7 +454,7 @@ export class Textarea {
508
454
  [`textarea-shape-${shape}`]: shape !== undefined,
509
455
  [`textarea-label-placement-${labelPlacement}`]: true,
510
456
  'textarea-disabled': disabled,
511
- }) }, 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()));
457
+ }) }, 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()));
512
458
  }
513
459
  static get is() { return "ion-textarea"; }
514
460
  static get encapsulation() { return "scoped"; }
@@ -1079,8 +1025,7 @@ export class Textarea {
1079
1025
  }
1080
1026
  static get states() {
1081
1027
  return {
1082
- "hasFocus": {},
1083
- "isInvalid": {}
1028
+ "hasFocus": {}
1084
1029
  };
1085
1030
  }
1086
1031
  static get events() {