@ionic/core 8.7.17-nightly.20260114 → 8.7.18-nightly.20260115

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.
@@ -47,6 +47,7 @@ const Footer = /*@__PURE__*/ proxyCustomElement(class Footer extends HTMLElement
47
47
  this.__registerHost();
48
48
  }
49
49
  this.keyboardCtrl = null;
50
+ this.keyboardCtrlPromise = null;
50
51
  this.keyboardVisible = false;
51
52
  /**
52
53
  * If `true`, the footer will be translucent.
@@ -94,7 +95,7 @@ const Footer = /*@__PURE__*/ proxyCustomElement(class Footer extends HTMLElement
94
95
  this.checkCollapsibleFooter();
95
96
  }
96
97
  async connectedCallback() {
97
- this.keyboardCtrl = await createKeyboardController(async (keyboardOpen, waitForResize) => {
98
+ const promise = createKeyboardController(async (keyboardOpen, waitForResize) => {
98
99
  /**
99
100
  * If the keyboard is hiding, then we need to wait
100
101
  * for the webview to resize. Otherwise, the footer
@@ -105,10 +106,29 @@ const Footer = /*@__PURE__*/ proxyCustomElement(class Footer extends HTMLElement
105
106
  }
106
107
  this.keyboardVisible = keyboardOpen; // trigger re-render by updating state
107
108
  });
109
+ this.keyboardCtrlPromise = promise;
110
+ const keyboardCtrl = await promise;
111
+ /**
112
+ * Only assign if this is still the current promise.
113
+ * Otherwise, a new connectedCallback has started or
114
+ * disconnectedCallback was called, so destroy this instance.
115
+ */
116
+ if (this.keyboardCtrlPromise === promise) {
117
+ this.keyboardCtrl = keyboardCtrl;
118
+ this.keyboardCtrlPromise = null;
119
+ }
120
+ else {
121
+ keyboardCtrl.destroy();
122
+ }
108
123
  }
109
124
  disconnectedCallback() {
125
+ if (this.keyboardCtrlPromise) {
126
+ this.keyboardCtrlPromise.then((ctrl) => ctrl.destroy());
127
+ this.keyboardCtrlPromise = null;
128
+ }
110
129
  if (this.keyboardCtrl) {
111
130
  this.keyboardCtrl.destroy();
131
+ this.keyboardCtrl = null;
112
132
  }
113
133
  }
114
134
  destroyCollapsibleFooter() {
@@ -122,7 +142,7 @@ const Footer = /*@__PURE__*/ proxyCustomElement(class Footer extends HTMLElement
122
142
  const mode = getIonMode(this);
123
143
  const tabs = this.el.closest('ion-tabs');
124
144
  const tabBar = tabs === null || tabs === void 0 ? void 0 : tabs.querySelector(':scope > ion-tab-bar');
125
- return (h(Host, { key: 'ddc228f1a1e7fa4f707dccf74db2490ca3241137', role: "contentinfo", class: {
145
+ return (h(Host, { key: '71939c4bbaef5062532a99ee2e33574102a9abad', role: "contentinfo", class: {
126
146
  [mode]: true,
127
147
  // Used internally for styling
128
148
  [`footer-${mode}`]: true,
@@ -130,7 +150,7 @@ const Footer = /*@__PURE__*/ proxyCustomElement(class Footer extends HTMLElement
130
150
  [`footer-translucent-${mode}`]: translucent,
131
151
  ['footer-toolbar-padding']: !this.keyboardVisible && (!tabBar || tabBar.slot !== 'bottom'),
132
152
  [`footer-collapse-${collapse}`]: collapse !== undefined,
133
- } }, mode === 'ios' && translucent && h("div", { key: 'e16ed4963ff94e06de77eb8038201820af73937c', class: "footer-background" }), h("slot", { key: 'f186934febf85d37133d9351a96c1a64b0a4b203' })));
153
+ } }, mode === 'ios' && translucent && h("div", { key: '2fa14f61661c47c661cecd696176728d6eafa74f', class: "footer-background" }), h("slot", { key: '8e63696e7c528d5c38201e546bf08135290d0945' })));
134
154
  }
135
155
  get el() { return this; }
136
156
  static get style() { return {
@@ -28,6 +28,7 @@ const Input = /*@__PURE__*/ proxyCustomElement(class Input extends HTMLElement {
28
28
  this.inputId = `ion-input-${inputIds++}`;
29
29
  this.helperTextId = `${this.inputId}-helper-text`;
30
30
  this.errorTextId = `${this.inputId}-error-text`;
31
+ this.labelTextId = `${this.inputId}-label`;
31
32
  this.inheritedAttributes = {};
32
33
  this.isComposing = false;
33
34
  /**
@@ -236,7 +237,11 @@ const Input = /*@__PURE__*/ proxyCustomElement(class Input extends HTMLElement {
236
237
  }
237
238
  connectedCallback() {
238
239
  const { el } = this;
239
- this.slotMutationController = createSlotMutationController(el, ['label', 'start', 'end'], () => forceUpdate(this));
240
+ this.slotMutationController = createSlotMutationController(el, ['label', 'start', 'end'], () => {
241
+ this.setSlottedLabelId();
242
+ forceUpdate(this);
243
+ });
244
+ this.setSlottedLabelId();
240
245
  this.notchController = createNotchController(el, () => this.notchSpacerEl, () => this.labelSlot);
241
246
  // Watch for class changes to update validation state
242
247
  if (Build.isBrowser && typeof MutationObserver !== 'undefined') {
@@ -439,11 +444,11 @@ const Input = /*@__PURE__*/ proxyCustomElement(class Input extends HTMLElement {
439
444
  return (h("div", { class: "input-bottom" }, this.renderHintText(), this.renderCounter()));
440
445
  }
441
446
  renderLabel() {
442
- const { label } = this;
447
+ const { label, labelTextId } = this;
443
448
  return (h("div", { class: {
444
449
  'label-text-wrapper': true,
445
450
  'label-text-wrapper-hidden': !this.hasLabel,
446
- } }, label === undefined ? h("slot", { name: "label" }) : h("div", { class: "label-text" }, label)));
451
+ }, "aria-hidden": this.hasLabel ? 'true' : null }, label === undefined ? (h("slot", { name: "label" })) : (h("div", { class: "label-text", id: labelTextId }, label))));
447
452
  }
448
453
  /**
449
454
  * Gets any content passed into the `label` slot,
@@ -452,6 +457,30 @@ const Input = /*@__PURE__*/ proxyCustomElement(class Input extends HTMLElement {
452
457
  get labelSlot() {
453
458
  return this.el.querySelector('[slot="label"]');
454
459
  }
460
+ /**
461
+ * Ensures the slotted label element has an ID for aria-labelledby.
462
+ * If no ID exists, we assign one using our generated labelTextId.
463
+ */
464
+ setSlottedLabelId() {
465
+ const slottedLabel = this.labelSlot;
466
+ if (slottedLabel && !slottedLabel.id) {
467
+ slottedLabel.id = this.labelTextId;
468
+ }
469
+ }
470
+ /**
471
+ * Returns the ID to use for aria-labelledby on the native input,
472
+ * or undefined if aria-label is explicitly set (to avoid conflicts).
473
+ */
474
+ getLabelledById() {
475
+ var _a;
476
+ if (this.inheritedAttributes['aria-label']) {
477
+ return undefined;
478
+ }
479
+ if (this.label !== undefined) {
480
+ return this.labelTextId;
481
+ }
482
+ return ((_a = this.labelSlot) === null || _a === void 0 ? void 0 : _a.id) || undefined;
483
+ }
455
484
  /**
456
485
  * Returns `true` if label content is provided
457
486
  * either by a prop or a content. If you want
@@ -518,7 +547,7 @@ const Input = /*@__PURE__*/ proxyCustomElement(class Input extends HTMLElement {
518
547
  * TODO(FW-5592): Remove hasStartEndSlots condition
519
548
  */
520
549
  const labelShouldFloat = labelPlacement === 'stacked' || (labelPlacement === 'floating' && (hasValue || hasFocus || hasStartEndSlots));
521
- return (h(Host, { key: '97b5308021064d9e7434ef2d3d96f27045c1b0c4', class: createColorClasses(this.color, {
550
+ return (h(Host, { key: '9ba9cf425b573d2ca9ac34455a0e6b8474c4de6d', class: createColorClasses(this.color, {
522
551
  [mode]: true,
523
552
  'has-value': hasValue,
524
553
  'has-focus': hasFocus,
@@ -529,14 +558,14 @@ const Input = /*@__PURE__*/ proxyCustomElement(class Input extends HTMLElement {
529
558
  'in-item': inItem,
530
559
  'in-item-color': hostContext('ion-item.ion-color', this.el),
531
560
  'input-disabled': disabled,
532
- }) }, 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) => {
561
+ }) }, h("label", { key: '74b989d0aa5ab38f29f952519868f05119df6005', class: "input-wrapper", htmlFor: inputId, onClick: this.onLabelClick }, this.renderLabelContainer(), h("div", { key: '47f2b42e2f74ea866b4f871026e08ab375d7a726', class: "native-wrapper", onClick: this.onLabelClick }, h("slot", { key: 'eaabe5a4a329a356cac3294d15c087d0d131fff2', name: "start" }), h("input", Object.assign({ key: 'c821a984a8a9b7f96f30892c06d8deda093ff24b', 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, "aria-labelledby": this.getLabelledById() }, this.inheritedAttributes)), this.clearInput && !readonly && !disabled && (h("button", { key: '62069c11016ee190dc46ab941372e1c4ad8a36ed', "aria-label": "reset", type: "button", class: "input-clear-icon", onPointerDown: (ev) => {
533
562
  /**
534
563
  * This prevents mobile browsers from
535
564
  * blurring the input when the clear
536
565
  * button is activated.
537
566
  */
538
567
  ev.preventDefault();
539
- }, 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()));
568
+ }, onClick: this.clearTextInput }, h("ion-icon", { key: 'dd75a516d32110d85382b664c663bd41f177ce12', "aria-hidden": "true", icon: clearIconData }))), h("slot", { key: '330d4b9389f2c62223a5ee24003e96ef3e6b2473', name: "end" })), shouldRenderHighlight && h("div", { key: '8e442bed130ddc84976ab70fd3f8578d6bcc6316', class: "input-highlight" })), this.renderBottomContent()));
540
569
  }
541
570
  get el() { return this; }
542
571
  static get watchers() { return {
@@ -20,6 +20,7 @@ const TabBar = /*@__PURE__*/ proxyCustomElement(class TabBar extends HTMLElement
20
20
  this.ionTabBarChanged = createEvent(this, "ionTabBarChanged", 7);
21
21
  this.ionTabBarLoaded = createEvent(this, "ionTabBarLoaded", 7);
22
22
  this.keyboardCtrl = null;
23
+ this.keyboardCtrlPromise = null;
23
24
  this.didLoad = false;
24
25
  this.keyboardVisible = false;
25
26
  /**
@@ -55,7 +56,7 @@ const TabBar = /*@__PURE__*/ proxyCustomElement(class TabBar extends HTMLElement
55
56
  }
56
57
  }
57
58
  async connectedCallback() {
58
- this.keyboardCtrl = await createKeyboardController(async (keyboardOpen, waitForResize) => {
59
+ const promise = createKeyboardController(async (keyboardOpen, waitForResize) => {
59
60
  /**
60
61
  * If the keyboard is hiding, then we need to wait
61
62
  * for the webview to resize. Otherwise, the tab bar
@@ -66,21 +67,40 @@ const TabBar = /*@__PURE__*/ proxyCustomElement(class TabBar extends HTMLElement
66
67
  }
67
68
  this.keyboardVisible = keyboardOpen; // trigger re-render by updating state
68
69
  });
70
+ this.keyboardCtrlPromise = promise;
71
+ const keyboardCtrl = await promise;
72
+ /**
73
+ * Only assign if this is still the current promise.
74
+ * Otherwise, a new connectedCallback has started or
75
+ * disconnectedCallback was called, so destroy this instance.
76
+ */
77
+ if (this.keyboardCtrlPromise === promise) {
78
+ this.keyboardCtrl = keyboardCtrl;
79
+ this.keyboardCtrlPromise = null;
80
+ }
81
+ else {
82
+ keyboardCtrl.destroy();
83
+ }
69
84
  }
70
85
  disconnectedCallback() {
86
+ if (this.keyboardCtrlPromise) {
87
+ this.keyboardCtrlPromise.then((ctrl) => ctrl.destroy());
88
+ this.keyboardCtrlPromise = null;
89
+ }
71
90
  if (this.keyboardCtrl) {
72
91
  this.keyboardCtrl.destroy();
92
+ this.keyboardCtrl = null;
73
93
  }
74
94
  }
75
95
  render() {
76
96
  const { color, translucent, keyboardVisible } = this;
77
97
  const mode = getIonMode(this);
78
98
  const shouldHide = keyboardVisible && this.el.getAttribute('slot') !== 'top';
79
- return (h(Host, { key: '388ec37ce308035bab78d6c9a016bb616e9517a9', role: "tablist", "aria-hidden": shouldHide ? 'true' : null, class: createColorClasses(color, {
99
+ return (h(Host, { key: '9daf4e2acaff6e3ce3878cf9dd5109fb1afbbebe', role: "tablist", "aria-hidden": shouldHide ? 'true' : null, class: createColorClasses(color, {
80
100
  [mode]: true,
81
101
  'tab-bar-translucent': translucent,
82
102
  'tab-bar-hidden': shouldHide,
83
- }) }, h("slot", { key: 'ce10ade2b86725e24f3254516483eeedd8ecb16a' })));
103
+ }) }, h("slot", { key: '1d15aa2da8501e8e7eff11ad4a491478be845c43' })));
84
104
  }
85
105
  get el() { return this; }
86
106
  static get watchers() { return {
@@ -626,6 +626,7 @@ const Footer = class {
626
626
  constructor(hostRef) {
627
627
  index.registerInstance(this, hostRef);
628
628
  this.keyboardCtrl = null;
629
+ this.keyboardCtrlPromise = null;
629
630
  this.keyboardVisible = false;
630
631
  /**
631
632
  * If `true`, the footer will be translucent.
@@ -673,7 +674,7 @@ const Footer = class {
673
674
  this.checkCollapsibleFooter();
674
675
  }
675
676
  async connectedCallback() {
676
- this.keyboardCtrl = await keyboardController.createKeyboardController(async (keyboardOpen, waitForResize) => {
677
+ const promise = keyboardController.createKeyboardController(async (keyboardOpen, waitForResize) => {
677
678
  /**
678
679
  * If the keyboard is hiding, then we need to wait
679
680
  * for the webview to resize. Otherwise, the footer
@@ -684,10 +685,29 @@ const Footer = class {
684
685
  }
685
686
  this.keyboardVisible = keyboardOpen; // trigger re-render by updating state
686
687
  });
688
+ this.keyboardCtrlPromise = promise;
689
+ const keyboardCtrl = await promise;
690
+ /**
691
+ * Only assign if this is still the current promise.
692
+ * Otherwise, a new connectedCallback has started or
693
+ * disconnectedCallback was called, so destroy this instance.
694
+ */
695
+ if (this.keyboardCtrlPromise === promise) {
696
+ this.keyboardCtrl = keyboardCtrl;
697
+ this.keyboardCtrlPromise = null;
698
+ }
699
+ else {
700
+ keyboardCtrl.destroy();
701
+ }
687
702
  }
688
703
  disconnectedCallback() {
704
+ if (this.keyboardCtrlPromise) {
705
+ this.keyboardCtrlPromise.then((ctrl) => ctrl.destroy());
706
+ this.keyboardCtrlPromise = null;
707
+ }
689
708
  if (this.keyboardCtrl) {
690
709
  this.keyboardCtrl.destroy();
710
+ this.keyboardCtrl = null;
691
711
  }
692
712
  }
693
713
  destroyCollapsibleFooter() {
@@ -701,7 +721,7 @@ const Footer = class {
701
721
  const mode = ionicGlobal.getIonMode(this);
702
722
  const tabs = this.el.closest('ion-tabs');
703
723
  const tabBar = tabs === null || tabs === void 0 ? void 0 : tabs.querySelector(':scope > ion-tab-bar');
704
- return (index.h(index.Host, { key: 'ddc228f1a1e7fa4f707dccf74db2490ca3241137', role: "contentinfo", class: {
724
+ return (index.h(index.Host, { key: '71939c4bbaef5062532a99ee2e33574102a9abad', role: "contentinfo", class: {
705
725
  [mode]: true,
706
726
  // Used internally for styling
707
727
  [`footer-${mode}`]: true,
@@ -709,7 +729,7 @@ const Footer = class {
709
729
  [`footer-translucent-${mode}`]: translucent,
710
730
  ['footer-toolbar-padding']: !this.keyboardVisible && (!tabBar || tabBar.slot !== 'bottom'),
711
731
  [`footer-collapse-${collapse}`]: collapse !== undefined,
712
- } }, mode === 'ios' && translucent && index.h("div", { key: 'e16ed4963ff94e06de77eb8038201820af73937c', class: "footer-background" }), index.h("slot", { key: 'f186934febf85d37133d9351a96c1a64b0a4b203' })));
732
+ } }, mode === 'ios' && translucent && index.h("div", { key: '2fa14f61661c47c661cecd696176728d6eafa74f', class: "footer-background" }), index.h("slot", { key: '8e63696e7c528d5c38201e546bf08135290d0945' })));
713
733
  }
714
734
  get el() { return index.getElement(this); }
715
735
  };
@@ -27,6 +27,7 @@ const Input = class {
27
27
  this.inputId = `ion-input-${inputIds++}`;
28
28
  this.helperTextId = `${this.inputId}-helper-text`;
29
29
  this.errorTextId = `${this.inputId}-error-text`;
30
+ this.labelTextId = `${this.inputId}-label`;
30
31
  this.inheritedAttributes = {};
31
32
  this.isComposing = false;
32
33
  /**
@@ -235,7 +236,11 @@ const Input = class {
235
236
  }
236
237
  connectedCallback() {
237
238
  const { el } = this;
238
- this.slotMutationController = input_utils.createSlotMutationController(el, ['label', 'start', 'end'], () => index.forceUpdate(this));
239
+ this.slotMutationController = input_utils.createSlotMutationController(el, ['label', 'start', 'end'], () => {
240
+ this.setSlottedLabelId();
241
+ index.forceUpdate(this);
242
+ });
243
+ this.setSlottedLabelId();
239
244
  this.notchController = notchController.createNotchController(el, () => this.notchSpacerEl, () => this.labelSlot);
240
245
  // Watch for class changes to update validation state
241
246
  if (typeof MutationObserver !== 'undefined') {
@@ -438,11 +443,11 @@ const Input = class {
438
443
  return (index.h("div", { class: "input-bottom" }, this.renderHintText(), this.renderCounter()));
439
444
  }
440
445
  renderLabel() {
441
- const { label } = this;
446
+ const { label, labelTextId } = this;
442
447
  return (index.h("div", { class: {
443
448
  'label-text-wrapper': true,
444
449
  'label-text-wrapper-hidden': !this.hasLabel,
445
- } }, label === undefined ? index.h("slot", { name: "label" }) : index.h("div", { class: "label-text" }, label)));
450
+ }, "aria-hidden": this.hasLabel ? 'true' : null }, label === undefined ? (index.h("slot", { name: "label" })) : (index.h("div", { class: "label-text", id: labelTextId }, label))));
446
451
  }
447
452
  /**
448
453
  * Gets any content passed into the `label` slot,
@@ -451,6 +456,30 @@ const Input = class {
451
456
  get labelSlot() {
452
457
  return this.el.querySelector('[slot="label"]');
453
458
  }
459
+ /**
460
+ * Ensures the slotted label element has an ID for aria-labelledby.
461
+ * If no ID exists, we assign one using our generated labelTextId.
462
+ */
463
+ setSlottedLabelId() {
464
+ const slottedLabel = this.labelSlot;
465
+ if (slottedLabel && !slottedLabel.id) {
466
+ slottedLabel.id = this.labelTextId;
467
+ }
468
+ }
469
+ /**
470
+ * Returns the ID to use for aria-labelledby on the native input,
471
+ * or undefined if aria-label is explicitly set (to avoid conflicts).
472
+ */
473
+ getLabelledById() {
474
+ var _a;
475
+ if (this.inheritedAttributes['aria-label']) {
476
+ return undefined;
477
+ }
478
+ if (this.label !== undefined) {
479
+ return this.labelTextId;
480
+ }
481
+ return ((_a = this.labelSlot) === null || _a === void 0 ? void 0 : _a.id) || undefined;
482
+ }
454
483
  /**
455
484
  * Returns `true` if label content is provided
456
485
  * either by a prop or a content. If you want
@@ -517,7 +546,7 @@ const Input = class {
517
546
  * TODO(FW-5592): Remove hasStartEndSlots condition
518
547
  */
519
548
  const labelShouldFloat = labelPlacement === 'stacked' || (labelPlacement === 'floating' && (hasValue || hasFocus || hasStartEndSlots));
520
- return (index.h(index.Host, { key: '97b5308021064d9e7434ef2d3d96f27045c1b0c4', class: theme.createColorClasses(this.color, {
549
+ return (index.h(index.Host, { key: '9ba9cf425b573d2ca9ac34455a0e6b8474c4de6d', class: theme.createColorClasses(this.color, {
521
550
  [mode]: true,
522
551
  'has-value': hasValue,
523
552
  'has-focus': hasFocus,
@@ -528,14 +557,14 @@ const Input = class {
528
557
  'in-item': inItem,
529
558
  'in-item-color': theme.hostContext('ion-item.ion-color', this.el),
530
559
  'input-disabled': disabled,
531
- }) }, index.h("label", { key: '353f68726ce180299bd9adc81e5ff7d26a48f54f', class: "input-wrapper", htmlFor: inputId, onClick: this.onLabelClick }, this.renderLabelContainer(), index.h("div", { key: '2034b4bad04fc157f3298a1805819216b6f439d0', class: "native-wrapper", onClick: this.onLabelClick }, index.h("slot", { key: '96bb5e30176b2bd76dfb75bfbf6c1c3d4403f4bb', name: "start" }), index.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 && (index.h("button", { key: '95f3df17b7691d9a2e7dcd4a51f16a94aa3ca36f', "aria-label": "reset", type: "button", class: "input-clear-icon", onPointerDown: (ev) => {
560
+ }) }, index.h("label", { key: '74b989d0aa5ab38f29f952519868f05119df6005', class: "input-wrapper", htmlFor: inputId, onClick: this.onLabelClick }, this.renderLabelContainer(), index.h("div", { key: '47f2b42e2f74ea866b4f871026e08ab375d7a726', class: "native-wrapper", onClick: this.onLabelClick }, index.h("slot", { key: 'eaabe5a4a329a356cac3294d15c087d0d131fff2', name: "start" }), index.h("input", Object.assign({ key: 'c821a984a8a9b7f96f30892c06d8deda093ff24b', 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, "aria-labelledby": this.getLabelledById() }, this.inheritedAttributes)), this.clearInput && !readonly && !disabled && (index.h("button", { key: '62069c11016ee190dc46ab941372e1c4ad8a36ed', "aria-label": "reset", type: "button", class: "input-clear-icon", onPointerDown: (ev) => {
532
561
  /**
533
562
  * This prevents mobile browsers from
534
563
  * blurring the input when the clear
535
564
  * button is activated.
536
565
  */
537
566
  ev.preventDefault();
538
- }, onClick: this.clearTextInput }, index.h("ion-icon", { key: '16b0af75eed50c8115fb5597f73b5fbf71c2530e', "aria-hidden": "true", icon: clearIconData }))), index.h("slot", { key: 'c48da0f8ddb3764ac43efa705bb4a6bb2d9cc2fd', name: "end" })), shouldRenderHighlight && index.h("div", { key: 'f15238481fc20de56ca7ecb6e350b3c024cc755e', class: "input-highlight" })), this.renderBottomContent()));
567
+ }, onClick: this.clearTextInput }, index.h("ion-icon", { key: 'dd75a516d32110d85382b664c663bd41f177ce12', "aria-hidden": "true", icon: clearIconData }))), index.h("slot", { key: '330d4b9389f2c62223a5ee24003e96ef3e6b2473', name: "end" })), shouldRenderHighlight && index.h("div", { key: '8e442bed130ddc84976ab70fd3f8578d6bcc6316', class: "input-highlight" })), this.renderBottomContent()));
539
568
  }
540
569
  get el() { return index.getElement(this); }
541
570
  static get watchers() { return {
@@ -22,6 +22,7 @@ const TabBar = class {
22
22
  this.ionTabBarChanged = index.createEvent(this, "ionTabBarChanged", 7);
23
23
  this.ionTabBarLoaded = index.createEvent(this, "ionTabBarLoaded", 7);
24
24
  this.keyboardCtrl = null;
25
+ this.keyboardCtrlPromise = null;
25
26
  this.didLoad = false;
26
27
  this.keyboardVisible = false;
27
28
  /**
@@ -57,7 +58,7 @@ const TabBar = class {
57
58
  }
58
59
  }
59
60
  async connectedCallback() {
60
- this.keyboardCtrl = await keyboardController.createKeyboardController(async (keyboardOpen, waitForResize) => {
61
+ const promise = keyboardController.createKeyboardController(async (keyboardOpen, waitForResize) => {
61
62
  /**
62
63
  * If the keyboard is hiding, then we need to wait
63
64
  * for the webview to resize. Otherwise, the tab bar
@@ -68,21 +69,40 @@ const TabBar = class {
68
69
  }
69
70
  this.keyboardVisible = keyboardOpen; // trigger re-render by updating state
70
71
  });
72
+ this.keyboardCtrlPromise = promise;
73
+ const keyboardCtrl = await promise;
74
+ /**
75
+ * Only assign if this is still the current promise.
76
+ * Otherwise, a new connectedCallback has started or
77
+ * disconnectedCallback was called, so destroy this instance.
78
+ */
79
+ if (this.keyboardCtrlPromise === promise) {
80
+ this.keyboardCtrl = keyboardCtrl;
81
+ this.keyboardCtrlPromise = null;
82
+ }
83
+ else {
84
+ keyboardCtrl.destroy();
85
+ }
71
86
  }
72
87
  disconnectedCallback() {
88
+ if (this.keyboardCtrlPromise) {
89
+ this.keyboardCtrlPromise.then((ctrl) => ctrl.destroy());
90
+ this.keyboardCtrlPromise = null;
91
+ }
73
92
  if (this.keyboardCtrl) {
74
93
  this.keyboardCtrl.destroy();
94
+ this.keyboardCtrl = null;
75
95
  }
76
96
  }
77
97
  render() {
78
98
  const { color, translucent, keyboardVisible } = this;
79
99
  const mode = ionicGlobal.getIonMode(this);
80
100
  const shouldHide = keyboardVisible && this.el.getAttribute('slot') !== 'top';
81
- return (index.h(index.Host, { key: '388ec37ce308035bab78d6c9a016bb616e9517a9', role: "tablist", "aria-hidden": shouldHide ? 'true' : null, class: theme.createColorClasses(color, {
101
+ return (index.h(index.Host, { key: '9daf4e2acaff6e3ce3878cf9dd5109fb1afbbebe', role: "tablist", "aria-hidden": shouldHide ? 'true' : null, class: theme.createColorClasses(color, {
82
102
  [mode]: true,
83
103
  'tab-bar-translucent': translucent,
84
104
  'tab-bar-hidden': shouldHide,
85
- }) }, index.h("slot", { key: 'ce10ade2b86725e24f3254516483eeedd8ecb16a' })));
105
+ }) }, index.h("slot", { key: '1d15aa2da8501e8e7eff11ad4a491478be845c43' })));
86
106
  }
87
107
  get el() { return index.getElement(this); }
88
108
  static get watchers() { return {
@@ -12,6 +12,7 @@ import { handleFooterFade } from "./footer.utils";
12
12
  export class Footer {
13
13
  constructor() {
14
14
  this.keyboardCtrl = null;
15
+ this.keyboardCtrlPromise = null;
15
16
  this.keyboardVisible = false;
16
17
  /**
17
18
  * If `true`, the footer will be translucent.
@@ -59,7 +60,7 @@ export class Footer {
59
60
  this.checkCollapsibleFooter();
60
61
  }
61
62
  async connectedCallback() {
62
- this.keyboardCtrl = await createKeyboardController(async (keyboardOpen, waitForResize) => {
63
+ const promise = createKeyboardController(async (keyboardOpen, waitForResize) => {
63
64
  /**
64
65
  * If the keyboard is hiding, then we need to wait
65
66
  * for the webview to resize. Otherwise, the footer
@@ -70,10 +71,29 @@ export class Footer {
70
71
  }
71
72
  this.keyboardVisible = keyboardOpen; // trigger re-render by updating state
72
73
  });
74
+ this.keyboardCtrlPromise = promise;
75
+ const keyboardCtrl = await promise;
76
+ /**
77
+ * Only assign if this is still the current promise.
78
+ * Otherwise, a new connectedCallback has started or
79
+ * disconnectedCallback was called, so destroy this instance.
80
+ */
81
+ if (this.keyboardCtrlPromise === promise) {
82
+ this.keyboardCtrl = keyboardCtrl;
83
+ this.keyboardCtrlPromise = null;
84
+ }
85
+ else {
86
+ keyboardCtrl.destroy();
87
+ }
73
88
  }
74
89
  disconnectedCallback() {
90
+ if (this.keyboardCtrlPromise) {
91
+ this.keyboardCtrlPromise.then((ctrl) => ctrl.destroy());
92
+ this.keyboardCtrlPromise = null;
93
+ }
75
94
  if (this.keyboardCtrl) {
76
95
  this.keyboardCtrl.destroy();
96
+ this.keyboardCtrl = null;
77
97
  }
78
98
  }
79
99
  destroyCollapsibleFooter() {
@@ -87,7 +107,7 @@ export class Footer {
87
107
  const mode = getIonMode(this);
88
108
  const tabs = this.el.closest('ion-tabs');
89
109
  const tabBar = tabs === null || tabs === void 0 ? void 0 : tabs.querySelector(':scope > ion-tab-bar');
90
- return (h(Host, { key: 'ddc228f1a1e7fa4f707dccf74db2490ca3241137', role: "contentinfo", class: {
110
+ return (h(Host, { key: '71939c4bbaef5062532a99ee2e33574102a9abad', role: "contentinfo", class: {
91
111
  [mode]: true,
92
112
  // Used internally for styling
93
113
  [`footer-${mode}`]: true,
@@ -95,7 +115,7 @@ export class Footer {
95
115
  [`footer-translucent-${mode}`]: translucent,
96
116
  ['footer-toolbar-padding']: !this.keyboardVisible && (!tabBar || tabBar.slot !== 'bottom'),
97
117
  [`footer-collapse-${collapse}`]: collapse !== undefined,
98
- } }, mode === 'ios' && translucent && h("div", { key: 'e16ed4963ff94e06de77eb8038201820af73937c', class: "footer-background" }), h("slot", { key: 'f186934febf85d37133d9351a96c1a64b0a4b203' })));
118
+ } }, mode === 'ios' && translucent && h("div", { key: '2fa14f61661c47c661cecd696176728d6eafa74f', class: "footer-background" }), h("slot", { key: '8e63696e7c528d5c38201e546bf08135290d0945' })));
99
119
  }
100
120
  static get is() { return "ion-footer"; }
101
121
  static get originalStyleUrls() {
@@ -21,6 +21,7 @@ export class Input {
21
21
  this.inputId = `ion-input-${inputIds++}`;
22
22
  this.helperTextId = `${this.inputId}-helper-text`;
23
23
  this.errorTextId = `${this.inputId}-error-text`;
24
+ this.labelTextId = `${this.inputId}-label`;
24
25
  this.inheritedAttributes = {};
25
26
  this.isComposing = false;
26
27
  /**
@@ -229,7 +230,11 @@ export class Input {
229
230
  }
230
231
  connectedCallback() {
231
232
  const { el } = this;
232
- this.slotMutationController = createSlotMutationController(el, ['label', 'start', 'end'], () => forceUpdate(this));
233
+ this.slotMutationController = createSlotMutationController(el, ['label', 'start', 'end'], () => {
234
+ this.setSlottedLabelId();
235
+ forceUpdate(this);
236
+ });
237
+ this.setSlottedLabelId();
233
238
  this.notchController = createNotchController(el, () => this.notchSpacerEl, () => this.labelSlot);
234
239
  // Watch for class changes to update validation state
235
240
  if (Build.isBrowser && typeof MutationObserver !== 'undefined') {
@@ -432,11 +437,11 @@ export class Input {
432
437
  return (h("div", { class: "input-bottom" }, this.renderHintText(), this.renderCounter()));
433
438
  }
434
439
  renderLabel() {
435
- const { label } = this;
440
+ const { label, labelTextId } = this;
436
441
  return (h("div", { class: {
437
442
  'label-text-wrapper': true,
438
443
  'label-text-wrapper-hidden': !this.hasLabel,
439
- } }, label === undefined ? h("slot", { name: "label" }) : h("div", { class: "label-text" }, label)));
444
+ }, "aria-hidden": this.hasLabel ? 'true' : null }, label === undefined ? (h("slot", { name: "label" })) : (h("div", { class: "label-text", id: labelTextId }, label))));
440
445
  }
441
446
  /**
442
447
  * Gets any content passed into the `label` slot,
@@ -445,6 +450,30 @@ export class Input {
445
450
  get labelSlot() {
446
451
  return this.el.querySelector('[slot="label"]');
447
452
  }
453
+ /**
454
+ * Ensures the slotted label element has an ID for aria-labelledby.
455
+ * If no ID exists, we assign one using our generated labelTextId.
456
+ */
457
+ setSlottedLabelId() {
458
+ const slottedLabel = this.labelSlot;
459
+ if (slottedLabel && !slottedLabel.id) {
460
+ slottedLabel.id = this.labelTextId;
461
+ }
462
+ }
463
+ /**
464
+ * Returns the ID to use for aria-labelledby on the native input,
465
+ * or undefined if aria-label is explicitly set (to avoid conflicts).
466
+ */
467
+ getLabelledById() {
468
+ var _a;
469
+ if (this.inheritedAttributes['aria-label']) {
470
+ return undefined;
471
+ }
472
+ if (this.label !== undefined) {
473
+ return this.labelTextId;
474
+ }
475
+ return ((_a = this.labelSlot) === null || _a === void 0 ? void 0 : _a.id) || undefined;
476
+ }
448
477
  /**
449
478
  * Returns `true` if label content is provided
450
479
  * either by a prop or a content. If you want
@@ -511,7 +540,7 @@ export class Input {
511
540
  * TODO(FW-5592): Remove hasStartEndSlots condition
512
541
  */
513
542
  const labelShouldFloat = labelPlacement === 'stacked' || (labelPlacement === 'floating' && (hasValue || hasFocus || hasStartEndSlots));
514
- return (h(Host, { key: '97b5308021064d9e7434ef2d3d96f27045c1b0c4', class: createColorClasses(this.color, {
543
+ return (h(Host, { key: '9ba9cf425b573d2ca9ac34455a0e6b8474c4de6d', class: createColorClasses(this.color, {
515
544
  [mode]: true,
516
545
  'has-value': hasValue,
517
546
  'has-focus': hasFocus,
@@ -522,14 +551,14 @@ export class Input {
522
551
  'in-item': inItem,
523
552
  'in-item-color': hostContext('ion-item.ion-color', this.el),
524
553
  'input-disabled': disabled,
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) => {
554
+ }) }, h("label", { key: '74b989d0aa5ab38f29f952519868f05119df6005', class: "input-wrapper", htmlFor: inputId, onClick: this.onLabelClick }, this.renderLabelContainer(), h("div", { key: '47f2b42e2f74ea866b4f871026e08ab375d7a726', class: "native-wrapper", onClick: this.onLabelClick }, h("slot", { key: 'eaabe5a4a329a356cac3294d15c087d0d131fff2', name: "start" }), h("input", Object.assign({ key: 'c821a984a8a9b7f96f30892c06d8deda093ff24b', 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, "aria-labelledby": this.getLabelledById() }, this.inheritedAttributes)), this.clearInput && !readonly && !disabled && (h("button", { key: '62069c11016ee190dc46ab941372e1c4ad8a36ed', "aria-label": "reset", type: "button", class: "input-clear-icon", onPointerDown: (ev) => {
526
555
  /**
527
556
  * This prevents mobile browsers from
528
557
  * blurring the input when the clear
529
558
  * button is activated.
530
559
  */
531
560
  ev.preventDefault();
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()));
561
+ }, onClick: this.clearTextInput }, h("ion-icon", { key: 'dd75a516d32110d85382b664c663bd41f177ce12', "aria-hidden": "true", icon: clearIconData }))), h("slot", { key: '330d4b9389f2c62223a5ee24003e96ef3e6b2473', name: "end" })), shouldRenderHighlight && h("div", { key: '8e442bed130ddc84976ab70fd3f8578d6bcc6316', class: "input-highlight" })), this.renderBottomContent()));
533
562
  }
534
563
  static get is() { return "ion-input"; }
535
564
  static get encapsulation() { return "scoped"; }