@ionic/core 8.7.7-nightly.20251015 → 8.7.8-nightly.20251016

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
@@ -152,4 +152,18 @@ const createNotchController = (el, getNotchSpacerEl, getLabelSlot) => {
152
152
  };
153
153
  };
154
154
 
155
+ /**
156
+ * Checks if the form element is in an invalid state based on
157
+ * Ionic validation classes.
158
+ *
159
+ * @param el The form element to check.
160
+ * @returns `true` if the element is invalid, `false` otherwise.
161
+ */
162
+ const checkInvalidState = (el) => {
163
+ const hasIonTouched = el.classList.contains('ion-touched');
164
+ const hasIonInvalid = el.classList.contains('ion-invalid');
165
+ return hasIonTouched && hasIonInvalid;
166
+ };
167
+
168
+ exports.checkInvalidState = checkInvalidState;
155
169
  exports.createNotchController = createNotchController;
@@ -216,11 +216,7 @@ export class Button {
216
216
  target,
217
217
  };
218
218
  let fill = this.fill;
219
- /**
220
- * We check both undefined and null to
221
- * work around https://github.com/ionic-team/stencil/issues/3586.
222
- */
223
- if (fill == null) {
219
+ if (fill === undefined) {
224
220
  fill = this.inToolbar || this.inListHeader ? 'clear' : 'solid';
225
221
  }
226
222
  /**
@@ -233,7 +229,7 @@ export class Button {
233
229
  {
234
230
  type !== 'button' && this.renderHiddenButton();
235
231
  }
236
- return (h(Host, { key: 'b105ad09215adb3ca2298acdadf0dc9154bbb9b0', onClick: this.handleClick, "aria-disabled": disabled ? 'true' : null, class: createColorClasses(color, {
232
+ return (h(Host, { key: 'ed82ea53705523f9afc5f1a9addff44cc6424f27', onClick: this.handleClick, "aria-disabled": disabled ? 'true' : null, class: createColorClasses(color, {
237
233
  [mode]: true,
238
234
  [buttonType]: true,
239
235
  [`${buttonType}-${expand}`]: expand !== undefined,
@@ -248,7 +244,7 @@ export class Button {
248
244
  'button-disabled': disabled,
249
245
  'ion-activatable': true,
250
246
  'ion-focusable': true,
251
- }) }, h(TagType, Object.assign({ key: '66b4e7112bcb9e41d5a723fbbadb0a3104f9ee1d' }, attrs, { class: "button-native", part: "native", disabled: disabled, onFocus: this.onFocus, onBlur: this.onBlur }, inheritedAttributes), h("span", { key: '1439fc3da280221028dcf7ce8ec9dab273c4d4bb', class: "button-inner" }, h("slot", { key: 'd5269ae1afc87ec7b99746032f59cbae93720a9f', name: "icon-only", onSlotchange: this.slotChanged }), h("slot", { key: '461c83e97aa246aa86d83e14f1e15a288d35041e', name: "start" }), h("slot", { key: '807170d47101f9f6a333dd4ff489c89284f306fe' }), h("slot", { key: 'e67f116dd0349a0d27893e4f3ff0ccef1d402f80', name: "end" })), mode === 'md' && h("ion-ripple-effect", { key: '273f0bd9645a36c1bfd18a5c2ab4f81e22b7b989', type: this.rippleType }))));
247
+ }) }, h(TagType, Object.assign({ key: 'fadec13053469dd0405bbbc61b70ced568aa4826' }, attrs, { class: "button-native", part: "native", disabled: disabled, onFocus: this.onFocus, onBlur: this.onBlur }, inheritedAttributes), h("span", { key: '6bf0e5144fb1148002e88038522402b789689d2c', class: "button-inner" }, h("slot", { key: '25da0ca155cfa9e2754842c34f4fd09f576ac2d2', name: "icon-only", onSlotchange: this.slotChanged }), h("slot", { key: '51414065bb11953ec9d818f8d9353589bc9072c5', name: "start" }), h("slot", { key: 'c9b5f8842aeabd20628df2f4600f1257ea913d8d' }), h("slot", { key: '478dd3671c7be1909fc84e672f0fa8dfe6082263', name: "end" })), mode === 'md' && h("ion-ripple-effect", { key: 'e1d55f85a55144d743f58a5914cd116cb065fa8c', type: this.rippleType }))));
252
248
  }
253
249
  static get is() { return "ion-button"; }
254
250
  static get encapsulation() { return "shadow"; }
@@ -6,7 +6,7 @@ import { findIonContent, getScrollElement, printIonContentErrorMsg } from "../..
6
6
  import { inheritAriaAttributes } from "../../utils/helpers";
7
7
  import { hostContext } from "../../utils/theme";
8
8
  import { getIonMode } from "../../global/ionic-global";
9
- import { cloneElement, createHeaderIndex, handleContentScroll, handleHeaderFade, handleToolbarIntersection, setHeaderActive, setToolbarBackgroundOpacity, } from "./header.utils";
9
+ import { cloneElement, createHeaderIndex, handleContentScroll, handleHeaderFade, handleToolbarIntersection, setHeaderActive, setToolbarBackgroundOpacity, getRoleType, } from "./header.utils";
10
10
  /**
11
11
  * @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
12
12
  */
@@ -145,16 +145,17 @@ export class Header {
145
145
  const { translucent, inheritedAttributes } = this;
146
146
  const mode = getIonMode(this);
147
147
  const collapse = this.collapse || 'none';
148
+ const isCondensed = collapse === 'condense';
148
149
  // banner role must be at top level, so remove role if inside a menu
149
- const roleType = hostContext('ion-menu', this.el) ? 'none' : 'banner';
150
- return (h(Host, Object.assign({ key: 'b6cc27f0b08afc9fcc889683525da765d80ba672', role: roleType, class: {
150
+ const roleType = getRoleType(hostContext('ion-menu', this.el), isCondensed, mode);
151
+ return (h(Host, Object.assign({ key: '863c4568cd7b8c0ec55109f193bbbaed68a1346e', role: roleType, class: {
151
152
  [mode]: true,
152
153
  // Used internally for styling
153
154
  [`header-${mode}`]: true,
154
155
  [`header-translucent`]: this.translucent,
155
156
  [`header-collapse-${collapse}`]: true,
156
157
  [`header-translucent-${mode}`]: this.translucent,
157
- } }, inheritedAttributes), mode === 'ios' && translucent && h("div", { key: '395766d4dcee3398bc91960db21f922095292f14', class: "header-background" }), h("slot", { key: '09a67ece27b258ff1248805d43d92a49b2c6859a' })));
158
+ } }, inheritedAttributes), mode === 'ios' && translucent && h("div", { key: '25c3bdce328b0b35607d154c8b8374679313d881', class: "header-background" }), h("slot", { key: 'b44fab0a9be7920b9650da26117c783e751e1702' })));
158
159
  }
159
160
  static get is() { return "ion-header"; }
160
161
  static get originalStyleUrls() {
@@ -4,6 +4,8 @@
4
4
  import { readTask, writeTask } from "@stencil/core";
5
5
  import { clamp } from "../../utils/helpers";
6
6
  const TRANSITION = 'all 0.2s ease-in-out';
7
+ const ROLE_NONE = 'none';
8
+ const ROLE_BANNER = 'banner';
7
9
  export const cloneElement = (tagName) => {
8
10
  const getCachedEl = document.querySelector(`${tagName}.ion-cloned-element`);
9
11
  if (getCachedEl !== null) {
@@ -130,6 +132,7 @@ export const setHeaderActive = (headerIndex, active = true) => {
130
132
  const toolbars = headerIndex.toolbars;
131
133
  const ionTitles = toolbars.map((toolbar) => toolbar.ionTitleEl);
132
134
  if (active) {
135
+ headerEl.setAttribute('role', ROLE_BANNER);
133
136
  headerEl.classList.remove('header-collapse-condense-inactive');
134
137
  ionTitles.forEach((ionTitle) => {
135
138
  if (ionTitle) {
@@ -138,6 +141,16 @@ export const setHeaderActive = (headerIndex, active = true) => {
138
141
  });
139
142
  }
140
143
  else {
144
+ /**
145
+ * There can only be one banner landmark per page.
146
+ * By default, all ion-headers have the banner role.
147
+ * This causes an accessibility issue when using a
148
+ * condensed header since there are two ion-headers
149
+ * on the page at once (active and inactive).
150
+ * To solve this, the role needs to be toggled
151
+ * based on which header is active.
152
+ */
153
+ headerEl.setAttribute('role', ROLE_NONE);
141
154
  headerEl.classList.add('header-collapse-condense-inactive');
142
155
  /**
143
156
  * The small title should only be accessed by screen readers
@@ -197,3 +210,27 @@ export const handleHeaderFade = (scrollEl, baseEl, condenseHeader) => {
197
210
  });
198
211
  });
199
212
  };
213
+ /**
214
+ * Get the role type for the ion-header.
215
+ *
216
+ * @param isInsideMenu If ion-header is inside ion-menu.
217
+ * @param isCondensed If ion-header has collapse="condense".
218
+ * @param mode The current mode.
219
+ * @returns 'none' if inside ion-menu or if condensed in md
220
+ * mode, otherwise 'banner'.
221
+ */
222
+ export const getRoleType = (isInsideMenu, isCondensed, mode) => {
223
+ // If the header is inside a menu, it should not have the banner role.
224
+ if (isInsideMenu) {
225
+ return ROLE_NONE;
226
+ }
227
+ /**
228
+ * Only apply role="none" to `md` mode condensed headers
229
+ * since the large header is never shown.
230
+ */
231
+ if (isCondensed && mode === 'md') {
232
+ return ROLE_NONE;
233
+ }
234
+ // Default to banner role.
235
+ return ROLE_BANNER;
236
+ };
@@ -2,7 +2,7 @@
2
2
  * (C) Ionic http://ionicframework.com - MIT License
3
3
  */
4
4
  import { Build, Host, forceUpdate, h, } from "@stencil/core";
5
- import { createNotchController } from "../../utils/forms/index";
5
+ import { createNotchController, checkInvalidState } from "../../utils/forms/index";
6
6
  import { inheritAriaAttributes, debounceEvent, inheritAttributes, componentOnReady } from "../../utils/helpers";
7
7
  import { createSlotMutationController } from "../../utils/slot-mutation-controller";
8
8
  import { createColorClasses, hostContext } from "../../utils/theme";
@@ -227,14 +227,6 @@ export class Input {
227
227
  componentWillLoad() {
228
228
  this.inheritedAttributes = Object.assign(Object.assign({}, inheritAriaAttributes(this.el)), inheritAttributes(this.el, ['tabindex', 'title', 'data-form-type', 'dir']));
229
229
  }
230
- /**
231
- * Checks if the input is in an invalid state based on Ionic validation classes
232
- */
233
- checkInvalidState() {
234
- const hasIonTouched = this.el.classList.contains('ion-touched');
235
- const hasIonInvalid = this.el.classList.contains('ion-invalid');
236
- return hasIonTouched && hasIonInvalid;
237
- }
238
230
  connectedCallback() {
239
231
  const { el } = this;
240
232
  this.slotMutationController = createSlotMutationController(el, ['label', 'start', 'end'], () => forceUpdate(this));
@@ -242,7 +234,7 @@ export class Input {
242
234
  // Watch for class changes to update validation state
243
235
  if (Build.isBrowser && typeof MutationObserver !== 'undefined') {
244
236
  this.validationObserver = new MutationObserver(() => {
245
- const newIsInvalid = this.checkInvalidState();
237
+ const newIsInvalid = checkInvalidState(el);
246
238
  if (this.isInvalid !== newIsInvalid) {
247
239
  this.isInvalid = newIsInvalid;
248
240
  // Force a re-render to update aria-describedby immediately
@@ -255,7 +247,7 @@ export class Input {
255
247
  });
256
248
  }
257
249
  // Always set initial state
258
- this.isInvalid = this.checkInvalidState();
250
+ this.isInvalid = checkInvalidState(el);
259
251
  this.debounceChanged();
260
252
  if (Build.isBrowser) {
261
253
  document.dispatchEvent(new CustomEvent('ionInputDidLoad', {
@@ -519,7 +511,7 @@ export class Input {
519
511
  * TODO(FW-5592): Remove hasStartEndSlots condition
520
512
  */
521
513
  const labelShouldFloat = labelPlacement === 'stacked' || (labelPlacement === 'floating' && (hasValue || hasFocus || hasStartEndSlots));
522
- return (h(Host, { key: '8a51f0300d5bc66392f9ab9a6fa0b5d388072a33', class: createColorClasses(this.color, {
514
+ return (h(Host, { key: '97b5308021064d9e7434ef2d3d96f27045c1b0c4', class: createColorClasses(this.color, {
523
515
  [mode]: true,
524
516
  'has-value': hasValue,
525
517
  'has-focus': hasFocus,
@@ -530,14 +522,14 @@ export class Input {
530
522
  'in-item': inItem,
531
523
  'in-item-color': hostContext('ion-item.ion-color', this.el),
532
524
  'input-disabled': disabled,
533
- }) }, h("label", { key: '9f8cf88d7d0e27931b51bd9c67f048c7fc6f5703', class: "input-wrapper", htmlFor: inputId, onClick: this.onLabelClick }, this.renderLabelContainer(), h("div", { key: '7ad30bf9777774062a6ccf9a3ba804f251eef1bb', class: "native-wrapper", onClick: this.onLabelClick }, h("slot", { key: '8af0b0325d101df8eed7d24f2767d6ca4d307319', name: "start" }), h("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 && (h("button", { key: 'b081d0e1ec1444b4c9cca145fc9cd2ad4a68b3da', "aria-label": "reset", type: "button", class: "input-clear-icon", onPointerDown: (ev) => {
525
+ }) }, h("label", { key: '353f68726ce180299bd9adc81e5ff7d26a48f54f', class: "input-wrapper", htmlFor: inputId, onClick: this.onLabelClick }, this.renderLabelContainer(), h("div", { key: '2034b4bad04fc157f3298a1805819216b6f439d0', class: "native-wrapper", onClick: this.onLabelClick }, h("slot", { key: '96bb5e30176b2bd76dfb75bfbf6c1c3d4403f4bb', name: "start" }), h("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 && (h("button", { key: '95f3df17b7691d9a2e7dcd4a51f16a94aa3ca36f', "aria-label": "reset", type: "button", class: "input-clear-icon", onPointerDown: (ev) => {
534
526
  /**
535
527
  * This prevents mobile browsers from
536
528
  * blurring the input when the clear
537
529
  * button is activated.
538
530
  */
539
531
  ev.preventDefault();
540
- }, onClick: this.clearTextInput }, h("ion-icon", { key: '01535299241c3635460c05646420acf62a1ff567', "aria-hidden": "true", icon: clearIconData }))), h("slot", { key: '480f3eb58b08ae792866a5b9b4c068748c5567cc', name: "end" })), shouldRenderHighlight && h("div", { key: 'a8609cacee88e4a09f1cca65b6a47cb79a56f35e', class: "input-highlight" })), this.renderBottomContent()));
532
+ }, onClick: this.clearTextInput }, h("ion-icon", { key: '16b0af75eed50c8115fb5597f73b5fbf71c2530e', "aria-hidden": "true", icon: clearIconData }))), h("slot", { key: 'c48da0f8ddb3764ac43efa705bb4a6bb2d9cc2fd', name: "end" })), shouldRenderHighlight && h("div", { key: 'f15238481fc20de56ca7ecb6e350b3c024cc755e', class: "input-highlight" })), this.renderBottomContent()));
541
533
  }
542
534
  static get is() { return "ion-input"; }
543
535
  static get encapsulation() { return "scoped"; }
@@ -1,8 +1,8 @@
1
1
  /*!
2
2
  * (C) Ionic http://ionicframework.com - MIT License
3
3
  */
4
- import { Host, h, forceUpdate } from "@stencil/core";
5
- import { compareOptions, createNotchController, isOptionSelected } from "../../utils/forms/index";
4
+ import { Build, Host, h, forceUpdate } from "@stencil/core";
5
+ import { compareOptions, createNotchController, isOptionSelected, checkInvalidState } from "../../utils/forms/index";
6
6
  import { focusVisibleElement, renderHiddenInput, inheritAttributes } from "../../utils/helpers";
7
7
  import { printIonWarning } from "../../utils/logging/index";
8
8
  import { actionSheetController, alertController, popoverController, modalController } from "../../utils/overlays";
@@ -44,6 +44,10 @@ export class Select {
44
44
  * is applied in both cases.
45
45
  */
46
46
  this.hasFocus = false;
47
+ /**
48
+ * Track validation state for proper aria-live announcements.
49
+ */
50
+ this.isInvalid = false;
47
51
  /**
48
52
  * The text to display on the cancel button.
49
53
  */
@@ -173,9 +177,46 @@ export class Select {
173
177
  */
174
178
  forceUpdate(this);
175
179
  });
180
+ // Watch for class changes to update validation state.
181
+ if (Build.isBrowser && typeof MutationObserver !== 'undefined') {
182
+ this.validationObserver = new MutationObserver(() => {
183
+ const newIsInvalid = checkInvalidState(this.el);
184
+ if (this.isInvalid !== newIsInvalid) {
185
+ this.isInvalid = newIsInvalid;
186
+ /**
187
+ * Screen readers tend to announce changes
188
+ * to `aria-describedby` when the attribute
189
+ * is changed during a blur event for a
190
+ * native form control.
191
+ * However, the announcement can be spotty
192
+ * when using a non-native form control
193
+ * and `forceUpdate()`.
194
+ * This is due to `forceUpdate()` internally
195
+ * rescheduling the DOM update to a lower
196
+ * priority queue regardless if it's called
197
+ * inside a Promise or not, thus causing
198
+ * the screen reader to potentially miss the
199
+ * change.
200
+ * By using a State variable inside a Promise,
201
+ * it guarantees a re-render immediately at
202
+ * a higher priority.
203
+ */
204
+ Promise.resolve().then(() => {
205
+ this.hintTextID = this.getHintTextID();
206
+ });
207
+ }
208
+ });
209
+ this.validationObserver.observe(el, {
210
+ attributes: true,
211
+ attributeFilter: ['class'],
212
+ });
213
+ }
214
+ // Always set initial state
215
+ this.isInvalid = checkInvalidState(this.el);
176
216
  }
177
217
  componentWillLoad() {
178
218
  this.inheritedAttributes = inheritAttributes(this.el, ['aria-label']);
219
+ this.hintTextID = this.getHintTextID();
179
220
  }
180
221
  componentDidLoad() {
181
222
  /**
@@ -199,6 +240,11 @@ export class Select {
199
240
  this.notchController.destroy();
200
241
  this.notchController = undefined;
201
242
  }
243
+ // Clean up validation observer to prevent memory leaks.
244
+ if (this.validationObserver) {
245
+ this.validationObserver.disconnect();
246
+ this.validationObserver = undefined;
247
+ }
202
248
  }
203
249
  /**
204
250
  * Open the select overlay. The overlay is either an alert, action sheet, or popover,
@@ -715,11 +761,11 @@ export class Select {
715
761
  }
716
762
  renderListbox() {
717
763
  const { disabled, inputId, isExpanded, required } = this;
718
- return (h("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) }));
764
+ return (h("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) }));
719
765
  }
720
766
  getHintTextID() {
721
- const { el, helperText, errorText, helperTextId, errorTextId } = this;
722
- if (el.classList.contains('ion-touched') && el.classList.contains('ion-invalid') && errorText) {
767
+ const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
768
+ if (isInvalid && errorText) {
723
769
  return errorTextId;
724
770
  }
725
771
  if (helperText) {
@@ -731,10 +777,10 @@ export class Select {
731
777
  * Renders the helper text or error text values
732
778
  */
733
779
  renderHintText() {
734
- const { helperText, errorText, helperTextId, errorTextId } = this;
780
+ const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
735
781
  return [
736
- h("div", { id: helperTextId, class: "helper-text", part: "supporting-text helper-text" }, helperText),
737
- h("div", { id: errorTextId, class: "error-text", part: "supporting-text error-text" }, errorText),
782
+ h("div", { id: helperTextId, class: "helper-text", part: "supporting-text helper-text", "aria-live": "polite" }, !isInvalid ? helperText : null),
783
+ h("div", { id: errorTextId, class: "error-text", part: "supporting-text error-text", role: "alert" }, isInvalid ? errorText : null),
738
784
  ];
739
785
  }
740
786
  /**
@@ -782,7 +828,7 @@ export class Select {
782
828
  * TODO(FW-5592): Remove hasStartEndSlots condition
783
829
  */
784
830
  const labelShouldFloat = labelPlacement === 'stacked' || (labelPlacement === 'floating' && (hasValue || isExpanded || hasStartEndSlots));
785
- return (h(Host, { key: 'c03fb65e8fc9f9aab295e07b282377d57d910519', onClick: this.onClick, class: createColorClasses(this.color, {
831
+ return (h(Host, { key: '35b5e18e6f79a802ff2d46d1242e80ff755cc0b9', onClick: this.onClick, class: createColorClasses(this.color, {
786
832
  [mode]: true,
787
833
  'in-item': inItem,
788
834
  'in-item-color': hostContext('ion-item.ion-color', el),
@@ -800,7 +846,7 @@ export class Select {
800
846
  [`select-justify-${justify}`]: justifyEnabled,
801
847
  [`select-shape-${shape}`]: shape !== undefined,
802
848
  [`select-label-placement-${labelPlacement}`]: true,
803
- }) }, h("label", { key: '0d0c8ec55269adcac625f2899a547f4e7f3e3741', class: "select-wrapper", id: "select-label", onClick: this.onLabelClick }, this.renderLabelContainer(), h("div", { key: 'f6dfc93c0e23cbe75a2947abde67d842db2dad78', class: "select-wrapper-inner" }, h("slot", { key: '957bfadf9f101f519091419a362d3abdc2be66f6', name: "start" }), h("div", { key: 'ca349202a484e7f2e884533fd330f0b136754f7d', class: "native-wrapper", ref: (el) => (this.nativeWrapperEl = el), part: "container" }, this.renderSelectText(), this.renderListbox()), h("slot", { key: 'f0e62a6533ff1c8f62bd2d27f60b23385c4fa9ed', name: "end" }), !hasFloatingOrStackedLabel && this.renderSelectIcon()), hasFloatingOrStackedLabel && this.renderSelectIcon(), shouldRenderHighlight && h("div", { key: 'fb840d46bafafb09898ebeebbe8c181906a3d8a2', class: "select-highlight" })), this.renderBottomContent()));
849
+ }) }, h("label", { key: '6005b34a0c50bc4d7653a4276bc232ecd02e083c', class: "select-wrapper", id: "select-label", onClick: this.onLabelClick }, this.renderLabelContainer(), h("div", { key: 'c7e07aa81ae856c057f16275dd058f37c5670a47', class: "select-wrapper-inner" }, h("slot", { key: '7fc2deefe0424404caacdbbd9e08ed43ba55d28a', name: "start" }), h("div", { key: '157d74ee717b1bc30b5f1c233a09b0c8456aa68e', class: "native-wrapper", ref: (el) => (this.nativeWrapperEl = el), part: "container" }, this.renderSelectText(), this.renderListbox()), h("slot", { key: 'ea66db304528b82bf9317730b6dce3db2612f235', name: "end" }), !hasFloatingOrStackedLabel && this.renderSelectIcon()), hasFloatingOrStackedLabel && this.renderSelectIcon(), shouldRenderHighlight && h("div", { key: '786eb1530b7476f0615d4e7c0bf4e7e4dc66509c', class: "select-highlight" })), this.renderBottomContent()));
804
850
  }
805
851
  static get is() { return "ion-select"; }
806
852
  static get encapsulation() { return "shadow"; }
@@ -1268,7 +1314,9 @@ export class Select {
1268
1314
  static get states() {
1269
1315
  return {
1270
1316
  "isExpanded": {},
1271
- "hasFocus": {}
1317
+ "hasFocus": {},
1318
+ "isInvalid": {},
1319
+ "hintTextID": {}
1272
1320
  };
1273
1321
  }
1274
1322
  static get events() {
@@ -2,7 +2,7 @@
2
2
  * (C) Ionic http://ionicframework.com - MIT License
3
3
  */
4
4
  import { Build, Host, forceUpdate, h, writeTask, } from "@stencil/core";
5
- import { createNotchController } from "../../utils/forms/index";
5
+ import { createNotchController, checkInvalidState } from "../../utils/forms/index";
6
6
  import { inheritAriaAttributes, debounceEvent, inheritAttributes, componentOnReady } from "../../utils/helpers";
7
7
  import { createSlotMutationController } from "../../utils/slot-mutation-controller";
8
8
  import { createColorClasses, hostContext } from "../../utils/theme";
@@ -187,14 +187,6 @@ export class Textarea {
187
187
  this.el.click();
188
188
  }
189
189
  }
190
- /**
191
- * Checks if the textarea is in an invalid state based on Ionic validation classes
192
- */
193
- checkValidationState() {
194
- const hasIonTouched = this.el.classList.contains('ion-touched');
195
- const hasIonInvalid = this.el.classList.contains('ion-invalid');
196
- return hasIonTouched && hasIonInvalid;
197
- }
198
190
  connectedCallback() {
199
191
  const { el } = this;
200
192
  this.slotMutationController = createSlotMutationController(el, ['label', 'start', 'end'], () => forceUpdate(this));
@@ -202,7 +194,7 @@ export class Textarea {
202
194
  // Watch for class changes to update validation state
203
195
  if (Build.isBrowser && typeof MutationObserver !== 'undefined') {
204
196
  this.validationObserver = new MutationObserver(() => {
205
- const newIsInvalid = this.checkValidationState();
197
+ const newIsInvalid = checkInvalidState(this.el);
206
198
  if (this.isInvalid !== newIsInvalid) {
207
199
  this.isInvalid = newIsInvalid;
208
200
  // Force a re-render to update aria-describedby immediately
@@ -215,7 +207,7 @@ export class Textarea {
215
207
  });
216
208
  }
217
209
  // Always set initial state
218
- this.isInvalid = this.checkValidationState();
210
+ this.isInvalid = checkInvalidState(this.el);
219
211
  this.debounceChanged();
220
212
  if (Build.isBrowser) {
221
213
  document.dispatchEvent(new CustomEvent('ionInputDidLoad', {
@@ -479,7 +471,7 @@ export class Textarea {
479
471
  * TODO(FW-5592): Remove hasStartEndSlots condition
480
472
  */
481
473
  const labelShouldFloat = labelPlacement === 'stacked' || (labelPlacement === 'floating' && (hasValue || hasFocus || hasStartEndSlots));
482
- return (h(Host, { key: '26b46666a92b3f652775bb1c46661f9a30392104', class: createColorClasses(this.color, {
474
+ return (h(Host, { key: 'a70a62d7aae3831a50acd74f60b930925ada1326', class: createColorClasses(this.color, {
483
475
  [mode]: true,
484
476
  'has-value': hasValue,
485
477
  'has-focus': hasFocus,
@@ -488,7 +480,7 @@ export class Textarea {
488
480
  [`textarea-shape-${shape}`]: shape !== undefined,
489
481
  [`textarea-label-placement-${labelPlacement}`]: true,
490
482
  'textarea-disabled': disabled,
491
- }) }, h("label", { key: '2649da816216959ebe1f34cafd9dedbac20ec3c2', class: "textarea-wrapper", htmlFor: inputId, onClick: this.onLabelClick }, this.renderLabelContainer(), h("div", { key: 'dca98593efece1b044dbcda045fa70882d715cb2', class: "textarea-wrapper-inner" }, h("div", { key: '2019daf87fddca5ec0b2e336f0376fd9642bae1b', class: "start-slot-wrapper" }, h("slot", { key: '36c423c394a71d08261705b9d6729e756bf65924', name: "start" })), h("div", { key: '0c3ea34105c7eddfa4094371c5d288c50ed10db3', class: "native-wrapper", ref: (el) => (this.textareaWrapper = el) }, h("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)), h("div", { key: '756e343cfd208bb5ad9ecf08d77cbb0a9606dc7b', class: "end-slot-wrapper" }, h("slot", { key: '0eb596814a037fa4634ff8c5bac0045540edfe21', name: "end" }))), shouldRenderHighlight && h("div", { key: 'df62f896eb6e0e2d1217aa487c198eb82a52bcb8', class: "textarea-highlight" })), this.renderBottomContent()));
483
+ }) }, h("label", { key: '8a2dd59a60f7469df84018eb0ede3a9ec3862703', class: "textarea-wrapper", htmlFor: inputId, onClick: this.onLabelClick }, this.renderLabelContainer(), h("div", { key: '1bfc368236e3da7a225a45118c27fbfc1fe5fa46', class: "textarea-wrapper-inner" }, h("div", { key: '215cbb2635ff52e31a8973376989b85e7245d40f', class: "start-slot-wrapper" }, h("slot", { key: '9f6b461cdee9d629deb695d2bea054ece2f32305', name: "start" })), h("div", { key: 'c1af35a2d5bc452bebe0b22a26d15ff52b4e9fc8', class: "native-wrapper", ref: (el) => (this.textareaWrapper = el) }, h("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)), h("div", { key: 'c053ea8b865d0e29763aed2e4939cc9c9e374c15', class: "end-slot-wrapper" }, h("slot", { key: '930aa641833b0df54b9ea10368fc2f46d5f491f6', name: "end" }))), shouldRenderHighlight && h("div", { key: '8d12597d15f5f429d80e8272ea99e64ed924e482', class: "textarea-highlight" })), this.renderBottomContent()));
492
484
  }
493
485
  static get is() { return "ion-textarea"; }
494
486
  static get encapsulation() { return "scoped"; }
@@ -3,3 +3,4 @@
3
3
  */
4
4
  export * from './notch-controller';
5
5
  export * from './compare-with-utils';
6
+ export * from './validity';
@@ -0,0 +1,15 @@
1
+ /*!
2
+ * (C) Ionic http://ionicframework.com - MIT License
3
+ */
4
+ /**
5
+ * Checks if the form element is in an invalid state based on
6
+ * Ionic validation classes.
7
+ *
8
+ * @param el The form element to check.
9
+ * @returns `true` if the element is invalid, `false` otherwise.
10
+ */
11
+ export const checkInvalidState = (el) => {
12
+ const hasIonTouched = el.classList.contains('ion-touched');
13
+ const hasIonInvalid = el.classList.contains('ion-invalid');
14
+ return hasIonTouched && hasIonInvalid;
15
+ };
package/dist/docs.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "timestamp": "2025-10-15T06:11:13",
2
+ "timestamp": "2025-10-16T06:11:15",
3
3
  "compiler": {
4
4
  "name": "@stencil/core",
5
5
  "version": "4.38.0",
@@ -708,6 +708,8 @@ Footer.style = {
708
708
  };
709
709
 
710
710
  const TRANSITION = 'all 0.2s ease-in-out';
711
+ const ROLE_NONE = 'none';
712
+ const ROLE_BANNER = 'banner';
711
713
  const cloneElement = (tagName) => {
712
714
  const getCachedEl = document.querySelector(`${tagName}.ion-cloned-element`);
713
715
  if (getCachedEl !== null) {
@@ -834,6 +836,7 @@ const setHeaderActive = (headerIndex, active = true) => {
834
836
  const toolbars = headerIndex.toolbars;
835
837
  const ionTitles = toolbars.map((toolbar) => toolbar.ionTitleEl);
836
838
  if (active) {
839
+ headerEl.setAttribute('role', ROLE_BANNER);
837
840
  headerEl.classList.remove('header-collapse-condense-inactive');
838
841
  ionTitles.forEach((ionTitle) => {
839
842
  if (ionTitle) {
@@ -842,6 +845,16 @@ const setHeaderActive = (headerIndex, active = true) => {
842
845
  });
843
846
  }
844
847
  else {
848
+ /**
849
+ * There can only be one banner landmark per page.
850
+ * By default, all ion-headers have the banner role.
851
+ * This causes an accessibility issue when using a
852
+ * condensed header since there are two ion-headers
853
+ * on the page at once (active and inactive).
854
+ * To solve this, the role needs to be toggled
855
+ * based on which header is active.
856
+ */
857
+ headerEl.setAttribute('role', ROLE_NONE);
845
858
  headerEl.classList.add('header-collapse-condense-inactive');
846
859
  /**
847
860
  * The small title should only be accessed by screen readers
@@ -901,6 +914,30 @@ const handleHeaderFade = (scrollEl, baseEl, condenseHeader) => {
901
914
  });
902
915
  });
903
916
  };
917
+ /**
918
+ * Get the role type for the ion-header.
919
+ *
920
+ * @param isInsideMenu If ion-header is inside ion-menu.
921
+ * @param isCondensed If ion-header has collapse="condense".
922
+ * @param mode The current mode.
923
+ * @returns 'none' if inside ion-menu or if condensed in md
924
+ * mode, otherwise 'banner'.
925
+ */
926
+ const getRoleType = (isInsideMenu, isCondensed, mode) => {
927
+ // If the header is inside a menu, it should not have the banner role.
928
+ if (isInsideMenu) {
929
+ return ROLE_NONE;
930
+ }
931
+ /**
932
+ * Only apply role="none" to `md` mode condensed headers
933
+ * since the large header is never shown.
934
+ */
935
+ if (isCondensed && mode === 'md') {
936
+ return ROLE_NONE;
937
+ }
938
+ // Default to banner role.
939
+ return ROLE_BANNER;
940
+ };
904
941
 
905
942
  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}";
906
943
 
@@ -1042,16 +1079,17 @@ const Header = class {
1042
1079
  const { translucent, inheritedAttributes } = this;
1043
1080
  const mode = getIonMode(this);
1044
1081
  const collapse = this.collapse || 'none';
1082
+ const isCondensed = collapse === 'condense';
1045
1083
  // banner role must be at top level, so remove role if inside a menu
1046
- const roleType = hostContext('ion-menu', this.el) ? 'none' : 'banner';
1047
- return (h(Host, Object.assign({ key: 'b6cc27f0b08afc9fcc889683525da765d80ba672', role: roleType, class: {
1084
+ const roleType = getRoleType(hostContext('ion-menu', this.el), isCondensed, mode);
1085
+ return (h(Host, Object.assign({ key: '863c4568cd7b8c0ec55109f193bbbaed68a1346e', role: roleType, class: {
1048
1086
  [mode]: true,
1049
1087
  // Used internally for styling
1050
1088
  [`header-${mode}`]: true,
1051
1089
  [`header-translucent`]: this.translucent,
1052
1090
  [`header-collapse-${collapse}`]: true,
1053
1091
  [`header-translucent-${mode}`]: this.translucent,
1054
- } }, inheritedAttributes), mode === 'ios' && translucent && h("div", { key: '395766d4dcee3398bc91960db21f922095292f14', class: "header-background" }), h("slot", { key: '09a67ece27b258ff1248805d43d92a49b2c6859a' })));
1092
+ } }, inheritedAttributes), mode === 'ios' && translucent && h("div", { key: '25c3bdce328b0b35607d154c8b8374679313d881', class: "header-background" }), h("slot", { key: 'b44fab0a9be7920b9650da26117c783e751e1702' })));
1055
1093
  }
1056
1094
  get el() { return getElement(this); }
1057
1095
  };
@@ -213,11 +213,7 @@ const Button = class {
213
213
  target,
214
214
  };
215
215
  let fill = this.fill;
216
- /**
217
- * We check both undefined and null to
218
- * work around https://github.com/ionic-team/stencil/issues/3586.
219
- */
220
- if (fill == null) {
216
+ if (fill === undefined) {
221
217
  fill = this.inToolbar || this.inListHeader ? 'clear' : 'solid';
222
218
  }
223
219
  /**
@@ -230,7 +226,7 @@ const Button = class {
230
226
  {
231
227
  type !== 'button' && this.renderHiddenButton();
232
228
  }
233
- return (h(Host, { key: 'b105ad09215adb3ca2298acdadf0dc9154bbb9b0', onClick: this.handleClick, "aria-disabled": disabled ? 'true' : null, class: createColorClasses$1(color, {
229
+ return (h(Host, { key: 'ed82ea53705523f9afc5f1a9addff44cc6424f27', onClick: this.handleClick, "aria-disabled": disabled ? 'true' : null, class: createColorClasses$1(color, {
234
230
  [mode]: true,
235
231
  [buttonType]: true,
236
232
  [`${buttonType}-${expand}`]: expand !== undefined,
@@ -245,7 +241,7 @@ const Button = class {
245
241
  'button-disabled': disabled,
246
242
  'ion-activatable': true,
247
243
  'ion-focusable': true,
248
- }) }, h(TagType, Object.assign({ key: '66b4e7112bcb9e41d5a723fbbadb0a3104f9ee1d' }, attrs, { class: "button-native", part: "native", disabled: disabled, onFocus: this.onFocus, onBlur: this.onBlur }, inheritedAttributes), h("span", { key: '1439fc3da280221028dcf7ce8ec9dab273c4d4bb', class: "button-inner" }, h("slot", { key: 'd5269ae1afc87ec7b99746032f59cbae93720a9f', name: "icon-only", onSlotchange: this.slotChanged }), h("slot", { key: '461c83e97aa246aa86d83e14f1e15a288d35041e', name: "start" }), h("slot", { key: '807170d47101f9f6a333dd4ff489c89284f306fe' }), h("slot", { key: 'e67f116dd0349a0d27893e4f3ff0ccef1d402f80', name: "end" })), mode === 'md' && h("ion-ripple-effect", { key: '273f0bd9645a36c1bfd18a5c2ab4f81e22b7b989', type: this.rippleType }))));
244
+ }) }, h(TagType, Object.assign({ key: 'fadec13053469dd0405bbbc61b70ced568aa4826' }, attrs, { class: "button-native", part: "native", disabled: disabled, onFocus: this.onFocus, onBlur: this.onBlur }, inheritedAttributes), h("span", { key: '6bf0e5144fb1148002e88038522402b789689d2c', class: "button-inner" }, h("slot", { key: '25da0ca155cfa9e2754842c34f4fd09f576ac2d2', name: "icon-only", onSlotchange: this.slotChanged }), h("slot", { key: '51414065bb11953ec9d818f8d9353589bc9072c5', name: "start" }), h("slot", { key: 'c9b5f8842aeabd20628df2f4600f1257ea913d8d' }), h("slot", { key: '478dd3671c7be1909fc84e672f0fa8dfe6082263', name: "end" })), mode === 'md' && h("ion-ripple-effect", { key: 'e1d55f85a55144d743f58a5914cd116cb065fa8c', type: this.rippleType }))));
249
245
  }
250
246
  get el() { return getElement(this); }
251
247
  static get watchers() { return {