@ionic/core 8.7.10-nightly.20251111 → 8.7.10-nightly.20251119

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 (56) hide show
  1. package/components/checkbox.js +63 -9
  2. package/components/ion-input.js +2 -1
  3. package/components/ion-select.js +7 -6
  4. package/components/ion-textarea.js +2 -1
  5. package/components/ion-toggle.js +62 -12
  6. package/components/notch-controller.js +153 -0
  7. package/components/radio-group.js +60 -7
  8. package/components/validity.js +1 -150
  9. package/dist/cjs/ion-checkbox.cjs.entry.js +60 -8
  10. package/dist/cjs/ion-input.cjs.entry.js +3 -2
  11. package/dist/cjs/ion-radio_2.cjs.entry.js +57 -6
  12. package/dist/cjs/ion-select_3.cjs.entry.js +7 -6
  13. package/dist/cjs/ion-textarea.cjs.entry.js +3 -2
  14. package/dist/cjs/ion-toggle.cjs.entry.js +58 -10
  15. package/dist/cjs/ionic.cjs.js +1 -1
  16. package/dist/cjs/loader.cjs.js +1 -1
  17. package/dist/cjs/{validity-C8QoAYT2.js → notch-controller-Bzqhjm4f.js} +0 -14
  18. package/dist/cjs/validity-BpS37YFM.js +19 -0
  19. package/dist/collection/components/checkbox/checkbox.js +67 -9
  20. package/dist/collection/components/radio-group/radio-group.js +64 -7
  21. package/dist/collection/components/select/select.js +5 -5
  22. package/dist/collection/components/toggle/toggle.js +62 -12
  23. package/dist/docs.json +1 -1
  24. package/dist/esm/ion-checkbox.entry.js +60 -8
  25. package/dist/esm/ion-input.entry.js +2 -1
  26. package/dist/esm/ion-radio_2.entry.js +57 -6
  27. package/dist/esm/ion-select_3.entry.js +6 -5
  28. package/dist/esm/ion-textarea.entry.js +2 -1
  29. package/dist/esm/ion-toggle.entry.js +58 -10
  30. package/dist/esm/ionic.js +1 -1
  31. package/dist/esm/loader.js +1 -1
  32. package/dist/esm/{validity-B8oWougr.js → notch-controller-BwelN_JM.js} +1 -14
  33. package/dist/esm/validity-DJztqcrH.js +17 -0
  34. package/dist/ionic/ionic.esm.js +1 -1
  35. package/dist/ionic/p-40c261a3.entry.js +4 -0
  36. package/dist/ionic/p-4e41ea20.entry.js +4 -0
  37. package/dist/ionic/p-7380261c.entry.js +4 -0
  38. package/dist/ionic/{p-DieJyvMP.js → p-DCv9sLH2.js} +1 -1
  39. package/dist/ionic/p-DJztqcrH.js +4 -0
  40. package/dist/ionic/p-c19f63d0.entry.js +4 -0
  41. package/dist/ionic/p-d1f54e28.entry.js +4 -0
  42. package/dist/ionic/p-d3014190.entry.js +4 -0
  43. package/dist/types/components/checkbox/checkbox.d.ts +9 -1
  44. package/dist/types/components/radio-group/radio-group.d.ts +9 -1
  45. package/dist/types/components/select/select.d.ts +2 -2
  46. package/dist/types/components/toggle/toggle.d.ts +7 -1
  47. package/dist/types/utils/forms/validity.d.ts +1 -1
  48. package/hydrate/index.js +277 -225
  49. package/hydrate/index.mjs +277 -225
  50. package/package.json +2 -2
  51. package/dist/ionic/p-4cc26913.entry.js +0 -4
  52. package/dist/ionic/p-7bcfc421.entry.js +0 -4
  53. package/dist/ionic/p-8bdfc8f6.entry.js +0 -4
  54. package/dist/ionic/p-dc2e126d.entry.js +0 -4
  55. package/dist/ionic/p-f65f9308.entry.js +0 -4
  56. package/dist/ionic/p-fc278823.entry.js +0 -4
package/hydrate/index.mjs CHANGED
@@ -9683,6 +9683,202 @@ class CardTitle {
9683
9683
  }; }
9684
9684
  }
9685
9685
 
9686
+ /**
9687
+ * A utility to calculate the size of an outline notch
9688
+ * width relative to the content passed. This is used in
9689
+ * components such as `ion-select` with `fill="outline"`
9690
+ * where we need to pass slotted HTML content. This is not
9691
+ * needed when rendering plaintext content because we can
9692
+ * render the plaintext again hidden with `opacity: 0` inside
9693
+ * of the notch. As a result we can rely on the intrinsic size
9694
+ * of the element to correctly compute the notch width. We
9695
+ * cannot do this with slotted content because we cannot project
9696
+ * it into 2 places at once.
9697
+ *
9698
+ * @internal
9699
+ * @param el: The host element
9700
+ * @param getNotchSpacerEl: A function that returns a reference to the notch spacer element inside of the component template.
9701
+ * @param getLabelSlot: A function that returns a reference to the slotted content.
9702
+ */
9703
+ const createNotchController = (el, getNotchSpacerEl, getLabelSlot) => {
9704
+ let notchVisibilityIO;
9705
+ const needsExplicitNotchWidth = () => {
9706
+ const notchSpacerEl = getNotchSpacerEl();
9707
+ if (
9708
+ /**
9709
+ * If the notch is not being used
9710
+ * then we do not need to set the notch width.
9711
+ */
9712
+ notchSpacerEl === undefined ||
9713
+ /**
9714
+ * If either the label property is being
9715
+ * used or the label slot is not defined,
9716
+ * then we do not need to estimate the notch width.
9717
+ */
9718
+ el.label !== undefined ||
9719
+ getLabelSlot() === null) {
9720
+ return false;
9721
+ }
9722
+ return true;
9723
+ };
9724
+ const calculateNotchWidth = () => {
9725
+ if (needsExplicitNotchWidth()) {
9726
+ /**
9727
+ * Run this the frame after
9728
+ * the browser has re-painted the host element.
9729
+ * Otherwise, the label element may have a width
9730
+ * of 0 and the IntersectionObserver will be used.
9731
+ */
9732
+ raf(() => {
9733
+ setNotchWidth();
9734
+ });
9735
+ }
9736
+ };
9737
+ /**
9738
+ * When using a label prop we can render
9739
+ * the label value inside of the notch and
9740
+ * let the browser calculate the size of the notch.
9741
+ * However, we cannot render the label slot in multiple
9742
+ * places so we need to manually calculate the notch dimension
9743
+ * based on the size of the slotted content.
9744
+ *
9745
+ * This function should only be used to set the notch width
9746
+ * on slotted label content. The notch width for label prop
9747
+ * content is automatically calculated based on the
9748
+ * intrinsic size of the label text.
9749
+ */
9750
+ const setNotchWidth = () => {
9751
+ const notchSpacerEl = getNotchSpacerEl();
9752
+ if (notchSpacerEl === undefined) {
9753
+ return;
9754
+ }
9755
+ if (!needsExplicitNotchWidth()) {
9756
+ notchSpacerEl.style.removeProperty('width');
9757
+ return;
9758
+ }
9759
+ const width = getLabelSlot().scrollWidth;
9760
+ if (
9761
+ /**
9762
+ * If the computed width of the label is 0
9763
+ * and notchSpacerEl's offsetParent is null
9764
+ * then that means the element is hidden.
9765
+ * As a result, we need to wait for the element
9766
+ * to become visible before setting the notch width.
9767
+ *
9768
+ * We do not check el.offsetParent because
9769
+ * that can be null if the host element has
9770
+ * position: fixed applied to it.
9771
+ * notchSpacerEl does not have position: fixed.
9772
+ */
9773
+ width === 0 &&
9774
+ notchSpacerEl.offsetParent === null &&
9775
+ win$1 !== undefined &&
9776
+ 'IntersectionObserver' in win$1) {
9777
+ /**
9778
+ * If there is an IO already attached
9779
+ * then that will update the notch
9780
+ * once the element becomes visible.
9781
+ * As a result, there is no need to create
9782
+ * another one.
9783
+ */
9784
+ if (notchVisibilityIO !== undefined) {
9785
+ return;
9786
+ }
9787
+ const io = (notchVisibilityIO = new IntersectionObserver((ev) => {
9788
+ /**
9789
+ * If the element is visible then we
9790
+ * can try setting the notch width again.
9791
+ */
9792
+ if (ev[0].intersectionRatio === 1) {
9793
+ setNotchWidth();
9794
+ io.disconnect();
9795
+ notchVisibilityIO = undefined;
9796
+ }
9797
+ },
9798
+ /**
9799
+ * Set the root to be the host element
9800
+ * This causes the IO callback
9801
+ * to be fired in WebKit as soon as the element
9802
+ * is visible. If we used the default root value
9803
+ * then WebKit would only fire the IO callback
9804
+ * after any animations (such as a modal transition)
9805
+ * finished, and there would potentially be a flicker.
9806
+ */
9807
+ { threshold: 0.01, root: el }));
9808
+ io.observe(notchSpacerEl);
9809
+ return;
9810
+ }
9811
+ /**
9812
+ * If the element is visible then we can set the notch width.
9813
+ * The notch is only visible when the label is scaled,
9814
+ * which is why we multiply the width by 0.75 as this is
9815
+ * the same amount the label element is scaled by in the host CSS.
9816
+ * (See $form-control-label-stacked-scale in ionic.globals.scss).
9817
+ */
9818
+ notchSpacerEl.style.setProperty('width', `${width * 0.75}px`);
9819
+ };
9820
+ const destroy = () => {
9821
+ if (notchVisibilityIO) {
9822
+ notchVisibilityIO.disconnect();
9823
+ notchVisibilityIO = undefined;
9824
+ }
9825
+ };
9826
+ return {
9827
+ calculateNotchWidth,
9828
+ destroy,
9829
+ };
9830
+ };
9831
+
9832
+ /**
9833
+ * Uses the compareWith param to compare two values to determine if they are equal.
9834
+ *
9835
+ * @param currentValue The current value of the control.
9836
+ * @param compareValue The value to compare against.
9837
+ * @param compareWith The function or property name to use to compare values.
9838
+ */
9839
+ const compareOptions = (currentValue, compareValue, compareWith) => {
9840
+ if (typeof compareWith === 'function') {
9841
+ return compareWith(currentValue, compareValue);
9842
+ }
9843
+ else if (typeof compareWith === 'string') {
9844
+ return currentValue[compareWith] === compareValue[compareWith];
9845
+ }
9846
+ else {
9847
+ return Array.isArray(compareValue) ? compareValue.includes(currentValue) : currentValue === compareValue;
9848
+ }
9849
+ };
9850
+ /**
9851
+ * Compares a value against the current value(s) to determine if it is selected.
9852
+ *
9853
+ * @param currentValue The current value of the control.
9854
+ * @param compareValue The value to compare against.
9855
+ * @param compareWith The function or property name to use to compare values.
9856
+ */
9857
+ const isOptionSelected = (currentValue, compareValue, compareWith) => {
9858
+ if (currentValue === undefined) {
9859
+ return false;
9860
+ }
9861
+ if (Array.isArray(currentValue)) {
9862
+ return currentValue.some((val) => compareOptions(val, compareValue, compareWith));
9863
+ }
9864
+ else {
9865
+ return compareOptions(currentValue, compareValue, compareWith);
9866
+ }
9867
+ };
9868
+
9869
+ /**
9870
+ * Checks if the form element is in an invalid state based on
9871
+ * Ionic validation classes.
9872
+ *
9873
+ * @param el The form element to check.
9874
+ * @returns `true` if the element is invalid, `false` otherwise.
9875
+ */
9876
+ const checkInvalidState = (el) => {
9877
+ const hasIonTouched = el.classList.contains('ion-touched');
9878
+ const hasIonInvalid = el.classList.contains('ion-invalid');
9879
+ return hasIonTouched && hasIonInvalid;
9880
+ };
9881
+
9686
9882
  const checkboxIosCss = ":host{--checkbox-background-checked:var(--ion-color-primary, #0054e9);--border-color-checked:var(--ion-color-primary, #0054e9);--checkmark-color:var(--ion-color-primary-contrast, #fff);--transition:none;display:inline-block;position:relative;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;z-index:2}:host(.in-item){-ms-flex:1 1 0px;flex:1 1 0;width:100%;height:100%}:host([slot=start]),:host([slot=end]){-ms-flex:initial;flex:initial;width:auto}:host(.ion-color){--checkbox-background-checked:var(--ion-color-base);--border-color-checked:var(--ion-color-base);--checkmark-color:var(--ion-color-contrast)}.checkbox-wrapper{display:-ms-flexbox;display:flex;-ms-flex-positive:1;flex-grow:1;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between;height:inherit;cursor:inherit}.label-text-wrapper{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}:host(.in-item) .label-text-wrapper,:host(.in-item:not(.checkbox-label-placement-stacked):not([slot])) .native-wrapper{margin-top:10px;margin-bottom:10px}:host(.in-item.checkbox-label-placement-stacked) .label-text-wrapper{margin-top:10px;margin-bottom:16px}:host(.in-item.checkbox-label-placement-stacked) .native-wrapper{margin-bottom:10px}.label-text-wrapper-hidden{display:none}input{display:none}.native-wrapper{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center}.checkbox-icon{border-radius:var(--border-radius);position:relative;width:var(--size);height:var(--size);-webkit-transition:var(--transition);transition:var(--transition);border-width:var(--border-width);border-style:var(--border-style);border-color:var(--border-color);background:var(--checkbox-background);-webkit-box-sizing:border-box;box-sizing:border-box}.checkbox-icon path{fill:none;stroke:var(--checkmark-color);stroke-width:var(--checkmark-width);opacity:0}.checkbox-bottom{padding-top:4px;display:-ms-flexbox;display:flex;-ms-flex-pack:justify;justify-content:space-between;font-size:0.75rem;white-space:normal}:host(.checkbox-label-placement-stacked) .checkbox-bottom{font-size:1rem}.checkbox-bottom .error-text{display:none;color:var(--ion-color-danger, #c5000f)}.checkbox-bottom .helper-text{display:block;color:var(--ion-color-step-700, var(--ion-text-color-step-300, #4d4d4d))}:host(.ion-touched.ion-invalid) .checkbox-bottom .error-text{display:block}:host(.ion-touched.ion-invalid) .checkbox-bottom .helper-text{display:none}:host(.checkbox-label-placement-start) .checkbox-wrapper{-ms-flex-direction:row;flex-direction:row}:host(.checkbox-label-placement-start) .label-text-wrapper{-webkit-margin-start:0;margin-inline-start:0;-webkit-margin-end:16px;margin-inline-end:16px}:host(.checkbox-label-placement-end) .checkbox-wrapper{-ms-flex-direction:row-reverse;flex-direction:row-reverse;-ms-flex-pack:start;justify-content:start}:host(.checkbox-label-placement-end) .label-text-wrapper{-webkit-margin-start:16px;margin-inline-start:16px;-webkit-margin-end:0;margin-inline-end:0}:host(.checkbox-label-placement-fixed) .label-text-wrapper{-webkit-margin-start:0;margin-inline-start:0;-webkit-margin-end:16px;margin-inline-end:16px}:host(.checkbox-label-placement-fixed) .label-text-wrapper{-ms-flex:0 0 100px;flex:0 0 100px;width:100px;min-width:100px;max-width:200px}:host(.checkbox-label-placement-stacked) .checkbox-wrapper{-ms-flex-direction:column;flex-direction:column;text-align:center}:host(.checkbox-label-placement-stacked) .label-text-wrapper{-webkit-transform:scale(0.75);transform:scale(0.75);margin-left:0;margin-right:0;margin-bottom:16px;max-width:calc(100% / 0.75)}:host(.checkbox-label-placement-stacked.checkbox-alignment-start) .label-text-wrapper{-webkit-transform-origin:left top;transform-origin:left top}:host-context([dir=rtl]):host(.checkbox-label-placement-stacked.checkbox-alignment-start) .label-text-wrapper,:host-context([dir=rtl]).checkbox-label-placement-stacked.checkbox-alignment-start .label-text-wrapper{-webkit-transform-origin:right top;transform-origin:right top}@supports selector(:dir(rtl)){:host(.checkbox-label-placement-stacked.checkbox-alignment-start:dir(rtl)) .label-text-wrapper{-webkit-transform-origin:right top;transform-origin:right top}}:host(.checkbox-label-placement-stacked.checkbox-alignment-center) .label-text-wrapper{-webkit-transform-origin:center top;transform-origin:center top}:host-context([dir=rtl]):host(.checkbox-label-placement-stacked.checkbox-alignment-center) .label-text-wrapper,:host-context([dir=rtl]).checkbox-label-placement-stacked.checkbox-alignment-center .label-text-wrapper{-webkit-transform-origin:calc(100% - center) top;transform-origin:calc(100% - center) top}@supports selector(:dir(rtl)){:host(.checkbox-label-placement-stacked.checkbox-alignment-center:dir(rtl)) .label-text-wrapper{-webkit-transform-origin:calc(100% - center) top;transform-origin:calc(100% - center) top}}:host(.checkbox-justify-space-between) .checkbox-wrapper{-ms-flex-pack:justify;justify-content:space-between}:host(.checkbox-justify-start) .checkbox-wrapper{-ms-flex-pack:start;justify-content:start}:host(.checkbox-justify-end) .checkbox-wrapper{-ms-flex-pack:end;justify-content:end}:host(.checkbox-alignment-start) .checkbox-wrapper{-ms-flex-align:start;align-items:start}:host(.checkbox-alignment-center) .checkbox-wrapper{-ms-flex-align:center;align-items:center}:host(.checkbox-justify-space-between),:host(.checkbox-justify-start),:host(.checkbox-justify-end),:host(.checkbox-alignment-start),:host(.checkbox-alignment-center){display:block}:host(.checkbox-checked) .checkbox-icon,:host(.checkbox-indeterminate) .checkbox-icon{border-color:var(--border-color-checked);background:var(--checkbox-background-checked)}:host(.checkbox-checked) .checkbox-icon path,:host(.checkbox-indeterminate) .checkbox-icon path{opacity:1}:host(.checkbox-disabled){pointer-events:none}:host{--border-radius:50%;--border-width:0.125rem;--border-style:solid;--border-color:rgba(var(--ion-text-color-rgb, 0, 0, 0), 0.23);--checkbox-background:var(--ion-item-background, var(--ion-background-color, #fff));--size:min(1.375rem, 55.836px);--checkmark-width:1.5px}:host(.checkbox-disabled){opacity:0.3}";
9687
9883
 
9688
9884
  const checkboxMdCss = ":host{--checkbox-background-checked:var(--ion-color-primary, #0054e9);--border-color-checked:var(--ion-color-primary, #0054e9);--checkmark-color:var(--ion-color-primary-contrast, #fff);--transition:none;display:inline-block;position:relative;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;z-index:2}:host(.in-item){-ms-flex:1 1 0px;flex:1 1 0;width:100%;height:100%}:host([slot=start]),:host([slot=end]){-ms-flex:initial;flex:initial;width:auto}:host(.ion-color){--checkbox-background-checked:var(--ion-color-base);--border-color-checked:var(--ion-color-base);--checkmark-color:var(--ion-color-contrast)}.checkbox-wrapper{display:-ms-flexbox;display:flex;-ms-flex-positive:1;flex-grow:1;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between;height:inherit;cursor:inherit}.label-text-wrapper{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}:host(.in-item) .label-text-wrapper,:host(.in-item:not(.checkbox-label-placement-stacked):not([slot])) .native-wrapper{margin-top:10px;margin-bottom:10px}:host(.in-item.checkbox-label-placement-stacked) .label-text-wrapper{margin-top:10px;margin-bottom:16px}:host(.in-item.checkbox-label-placement-stacked) .native-wrapper{margin-bottom:10px}.label-text-wrapper-hidden{display:none}input{display:none}.native-wrapper{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center}.checkbox-icon{border-radius:var(--border-radius);position:relative;width:var(--size);height:var(--size);-webkit-transition:var(--transition);transition:var(--transition);border-width:var(--border-width);border-style:var(--border-style);border-color:var(--border-color);background:var(--checkbox-background);-webkit-box-sizing:border-box;box-sizing:border-box}.checkbox-icon path{fill:none;stroke:var(--checkmark-color);stroke-width:var(--checkmark-width);opacity:0}.checkbox-bottom{padding-top:4px;display:-ms-flexbox;display:flex;-ms-flex-pack:justify;justify-content:space-between;font-size:0.75rem;white-space:normal}:host(.checkbox-label-placement-stacked) .checkbox-bottom{font-size:1rem}.checkbox-bottom .error-text{display:none;color:var(--ion-color-danger, #c5000f)}.checkbox-bottom .helper-text{display:block;color:var(--ion-color-step-700, var(--ion-text-color-step-300, #4d4d4d))}:host(.ion-touched.ion-invalid) .checkbox-bottom .error-text{display:block}:host(.ion-touched.ion-invalid) .checkbox-bottom .helper-text{display:none}:host(.checkbox-label-placement-start) .checkbox-wrapper{-ms-flex-direction:row;flex-direction:row}:host(.checkbox-label-placement-start) .label-text-wrapper{-webkit-margin-start:0;margin-inline-start:0;-webkit-margin-end:16px;margin-inline-end:16px}:host(.checkbox-label-placement-end) .checkbox-wrapper{-ms-flex-direction:row-reverse;flex-direction:row-reverse;-ms-flex-pack:start;justify-content:start}:host(.checkbox-label-placement-end) .label-text-wrapper{-webkit-margin-start:16px;margin-inline-start:16px;-webkit-margin-end:0;margin-inline-end:0}:host(.checkbox-label-placement-fixed) .label-text-wrapper{-webkit-margin-start:0;margin-inline-start:0;-webkit-margin-end:16px;margin-inline-end:16px}:host(.checkbox-label-placement-fixed) .label-text-wrapper{-ms-flex:0 0 100px;flex:0 0 100px;width:100px;min-width:100px;max-width:200px}:host(.checkbox-label-placement-stacked) .checkbox-wrapper{-ms-flex-direction:column;flex-direction:column;text-align:center}:host(.checkbox-label-placement-stacked) .label-text-wrapper{-webkit-transform:scale(0.75);transform:scale(0.75);margin-left:0;margin-right:0;margin-bottom:16px;max-width:calc(100% / 0.75)}:host(.checkbox-label-placement-stacked.checkbox-alignment-start) .label-text-wrapper{-webkit-transform-origin:left top;transform-origin:left top}:host-context([dir=rtl]):host(.checkbox-label-placement-stacked.checkbox-alignment-start) .label-text-wrapper,:host-context([dir=rtl]).checkbox-label-placement-stacked.checkbox-alignment-start .label-text-wrapper{-webkit-transform-origin:right top;transform-origin:right top}@supports selector(:dir(rtl)){:host(.checkbox-label-placement-stacked.checkbox-alignment-start:dir(rtl)) .label-text-wrapper{-webkit-transform-origin:right top;transform-origin:right top}}:host(.checkbox-label-placement-stacked.checkbox-alignment-center) .label-text-wrapper{-webkit-transform-origin:center top;transform-origin:center top}:host-context([dir=rtl]):host(.checkbox-label-placement-stacked.checkbox-alignment-center) .label-text-wrapper,:host-context([dir=rtl]).checkbox-label-placement-stacked.checkbox-alignment-center .label-text-wrapper{-webkit-transform-origin:calc(100% - center) top;transform-origin:calc(100% - center) top}@supports selector(:dir(rtl)){:host(.checkbox-label-placement-stacked.checkbox-alignment-center:dir(rtl)) .label-text-wrapper{-webkit-transform-origin:calc(100% - center) top;transform-origin:calc(100% - center) top}}:host(.checkbox-justify-space-between) .checkbox-wrapper{-ms-flex-pack:justify;justify-content:space-between}:host(.checkbox-justify-start) .checkbox-wrapper{-ms-flex-pack:start;justify-content:start}:host(.checkbox-justify-end) .checkbox-wrapper{-ms-flex-pack:end;justify-content:end}:host(.checkbox-alignment-start) .checkbox-wrapper{-ms-flex-align:start;align-items:start}:host(.checkbox-alignment-center) .checkbox-wrapper{-ms-flex-align:center;align-items:center}:host(.checkbox-justify-space-between),:host(.checkbox-justify-start),:host(.checkbox-justify-end),:host(.checkbox-alignment-start),:host(.checkbox-alignment-center){display:block}:host(.checkbox-checked) .checkbox-icon,:host(.checkbox-indeterminate) .checkbox-icon{border-color:var(--border-color-checked);background:var(--checkbox-background-checked)}:host(.checkbox-checked) .checkbox-icon path,:host(.checkbox-indeterminate) .checkbox-icon path{opacity:1}:host(.checkbox-disabled){pointer-events:none}:host{--border-radius:calc(var(--size) * .125);--border-width:2px;--border-style:solid;--border-color:rgb(var(--ion-text-color-rgb, 0, 0, 0), 0.6);--checkmark-width:3;--checkbox-background:var(--ion-item-background, var(--ion-background-color, #fff));--transition:background 180ms cubic-bezier(0.4, 0, 0.2, 1);--size:18px}.checkbox-icon path{stroke-dasharray:30;stroke-dashoffset:30}:host(.checkbox-checked) .checkbox-icon path,:host(.checkbox-indeterminate) .checkbox-icon path{stroke-dashoffset:0;-webkit-transition:stroke-dashoffset 90ms linear 90ms;transition:stroke-dashoffset 90ms linear 90ms}:host(.checkbox-disabled) .label-text-wrapper{opacity:0.38}:host(.checkbox-disabled) .native-wrapper{opacity:0.63}";
@@ -9748,6 +9944,10 @@ class Checkbox {
9748
9944
  * submitting if the value is invalid.
9749
9945
  */
9750
9946
  this.required = false;
9947
+ /**
9948
+ * Track validation state for proper aria-live announcements.
9949
+ */
9950
+ this.isInvalid = false;
9751
9951
  /**
9752
9952
  * Sets the checked property and emits
9753
9953
  * the ionChange event. Use this to update the
@@ -9794,16 +9994,29 @@ class Checkbox {
9794
9994
  ev.stopPropagation();
9795
9995
  };
9796
9996
  }
9997
+ connectedCallback() {
9998
+ const { el } = this;
9999
+ // Always set initial state
10000
+ this.isInvalid = checkInvalidState(el);
10001
+ }
9797
10002
  componentWillLoad() {
9798
10003
  this.inheritedAttributes = Object.assign({}, inheritAriaAttributes(this.el));
10004
+ this.hintTextId = this.getHintTextId();
10005
+ }
10006
+ disconnectedCallback() {
10007
+ // Clean up validation observer to prevent memory leaks.
10008
+ if (this.validationObserver) {
10009
+ this.validationObserver.disconnect();
10010
+ this.validationObserver = undefined;
10011
+ }
9799
10012
  }
9800
10013
  /** @internal */
9801
10014
  async setFocus() {
9802
10015
  this.el.focus();
9803
10016
  }
9804
- getHintTextID() {
9805
- const { el, helperText, errorText, helperTextId, errorTextId } = this;
9806
- if (el.classList.contains('ion-touched') && el.classList.contains('ion-invalid') && errorText) {
10017
+ getHintTextId() {
10018
+ const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
10019
+ if (isInvalid && errorText) {
9807
10020
  return errorTextId;
9808
10021
  }
9809
10022
  if (helperText) {
@@ -9816,7 +10029,7 @@ class Checkbox {
9816
10029
  * This element should only be rendered if hint text is set.
9817
10030
  */
9818
10031
  renderHintText() {
9819
- const { helperText, errorText, helperTextId, errorTextId } = this;
10032
+ const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
9820
10033
  /**
9821
10034
  * undefined and empty string values should
9822
10035
  * be treated as not having helper/error text.
@@ -9825,7 +10038,7 @@ class Checkbox {
9825
10038
  if (!hasHintText) {
9826
10039
  return;
9827
10040
  }
9828
- return (hAsync("div", { class: "checkbox-bottom" }, hAsync("div", { id: helperTextId, class: "helper-text", part: "supporting-text helper-text" }, helperText), hAsync("div", { id: errorTextId, class: "error-text", part: "supporting-text error-text" }, errorText)));
10041
+ return (hAsync("div", { class: "checkbox-bottom" }, hAsync("div", { id: helperTextId, class: "helper-text", part: "supporting-text helper-text", "aria-live": "polite" }, !isInvalid ? helperText : null), hAsync("div", { id: errorTextId, class: "error-text", part: "supporting-text error-text", role: "alert" }, isInvalid ? errorText : null)));
9829
10042
  }
9830
10043
  render() {
9831
10044
  const { color, checked, disabled, el, getSVGPath, indeterminate, inheritedAttributes, inputId, justify, labelPlacement, name, value, alignment, required, } = this;
@@ -9835,7 +10048,7 @@ class Checkbox {
9835
10048
  renderHiddenInput(true, el, name, checked ? value : '', disabled);
9836
10049
  // The host element must have a checkbox role to ensure proper VoiceOver
9837
10050
  // support in Safari for accessibility.
9838
- return (hAsync(Host, { key: 'ee2e02d28f9d15a1ec746609f7e9559444f621e5', role: "checkbox", "aria-checked": indeterminate ? 'mixed' : `${checked}`, "aria-describedby": this.getHintTextID(), "aria-invalid": this.getHintTextID() === this.errorTextId, "aria-labelledby": hasLabelContent ? this.inputLabelId : null, "aria-label": inheritedAttributes['aria-label'] || null, "aria-disabled": disabled ? 'true' : null, tabindex: disabled ? undefined : 0, onKeyDown: this.onKeyDown, onFocus: this.onFocus, onBlur: this.onBlur, onClick: this.onClick, class: createColorClasses$1(color, {
10051
+ return (hAsync(Host, { key: 'ae0fbd4b21accbac132e6b85c513512ad9179394', role: "checkbox", "aria-checked": indeterminate ? 'mixed' : `${checked}`, "aria-describedby": this.hintTextId, "aria-invalid": this.isInvalid ? 'true' : undefined, "aria-labelledby": hasLabelContent ? this.inputLabelId : null, "aria-label": inheritedAttributes['aria-label'] || null, "aria-disabled": disabled ? 'true' : null, "aria-required": required ? 'true' : undefined, tabindex: disabled ? undefined : 0, onKeyDown: this.onKeyDown, onFocus: this.onFocus, onBlur: this.onBlur, onClick: this.onClick, class: createColorClasses$1(color, {
9839
10052
  [mode]: true,
9840
10053
  'in-item': hostContext('ion-item', el),
9841
10054
  'checkbox-checked': checked,
@@ -9845,10 +10058,10 @@ class Checkbox {
9845
10058
  [`checkbox-justify-${justify}`]: justify !== undefined,
9846
10059
  [`checkbox-alignment-${alignment}`]: alignment !== undefined,
9847
10060
  [`checkbox-label-placement-${labelPlacement}`]: true,
9848
- }) }, hAsync("label", { key: '84d4c33da0348dc65ad36fb0fafd48be366dcf3b', class: "checkbox-wrapper", htmlFor: inputId }, hAsync("input", Object.assign({ key: '427db69a3ab8a17aa0867519c90f585b8930406b', type: "checkbox", checked: checked ? true : undefined, disabled: disabled, id: inputId, onChange: this.toggleChecked, required: required }, inheritedAttributes)), hAsync("div", { key: '9dda7024b3a4f1ee55351f783f9a10f9b4ad0d12', class: {
10061
+ }) }, hAsync("label", { key: '7a3d7f3c27dde514f2dbf2e34f4629fad33ec3bf', class: "checkbox-wrapper", htmlFor: inputId }, hAsync("input", Object.assign({ key: '4130d77ddf034271fecccda14e101a5a809921b6', type: "checkbox", checked: checked ? true : undefined, disabled: disabled, id: inputId, onChange: this.toggleChecked, required: required }, inheritedAttributes)), hAsync("div", { key: '5daa74f4e62b0947e37764762524001ee42609d9', class: {
9849
10062
  'label-text-wrapper': true,
9850
10063
  'label-text-wrapper-hidden': !hasLabelContent,
9851
- }, part: "label", id: this.inputLabelId, onClick: this.onDivLabelClick }, hAsync("slot", { key: 'f9d1d545ffd4164b650808241b51ea1bedc6a42c' }), this.renderHintText()), hAsync("div", { key: 'a96d61ac324864228f14caa0e9f2c0d15418882e', class: "native-wrapper" }, hAsync("svg", { key: '64ff3e4d87e190601811ef64323edec18d510cd1', class: "checkbox-icon", viewBox: "0 0 24 24", part: "container", "aria-hidden": "true" }, path)))));
10064
+ }, part: "label", id: this.inputLabelId, onClick: this.onDivLabelClick }, hAsync("slot", { key: '23ff66138f8c3a2f56f39113fc842d54b2f7952a' }), this.renderHintText()), hAsync("div", { key: 'ab914d9623c19fc46821d5e62db92f1192ebbe7e', class: "native-wrapper" }, hAsync("svg", { key: '66e3f4f5dcaa9756fb0e9452299954f9ed3dcb7b', class: "checkbox-icon", viewBox: "0 0 24 24", part: "container", "aria-hidden": "true" }, path)))));
9852
10065
  }
9853
10066
  getSVGPath(mode, indeterminate) {
9854
10067
  let path = indeterminate ? (hAsync("path", { d: "M6 12L18 12", part: "mark" })) : (hAsync("path", { d: "M5.9,12.5l3.8,3.8l8.8-8.8", part: "mark" }));
@@ -9878,6 +10091,8 @@ class Checkbox {
9878
10091
  "justify": [1],
9879
10092
  "alignment": [1],
9880
10093
  "required": [4],
10094
+ "isInvalid": [32],
10095
+ "hintTextId": [32],
9881
10096
  "setFocus": [64]
9882
10097
  },
9883
10098
  "$listeners$": undefined,
@@ -16312,202 +16527,6 @@ class InfiniteScrollContent {
16312
16527
  }; }
16313
16528
  }
16314
16529
 
16315
- /**
16316
- * A utility to calculate the size of an outline notch
16317
- * width relative to the content passed. This is used in
16318
- * components such as `ion-select` with `fill="outline"`
16319
- * where we need to pass slotted HTML content. This is not
16320
- * needed when rendering plaintext content because we can
16321
- * render the plaintext again hidden with `opacity: 0` inside
16322
- * of the notch. As a result we can rely on the intrinsic size
16323
- * of the element to correctly compute the notch width. We
16324
- * cannot do this with slotted content because we cannot project
16325
- * it into 2 places at once.
16326
- *
16327
- * @internal
16328
- * @param el: The host element
16329
- * @param getNotchSpacerEl: A function that returns a reference to the notch spacer element inside of the component template.
16330
- * @param getLabelSlot: A function that returns a reference to the slotted content.
16331
- */
16332
- const createNotchController = (el, getNotchSpacerEl, getLabelSlot) => {
16333
- let notchVisibilityIO;
16334
- const needsExplicitNotchWidth = () => {
16335
- const notchSpacerEl = getNotchSpacerEl();
16336
- if (
16337
- /**
16338
- * If the notch is not being used
16339
- * then we do not need to set the notch width.
16340
- */
16341
- notchSpacerEl === undefined ||
16342
- /**
16343
- * If either the label property is being
16344
- * used or the label slot is not defined,
16345
- * then we do not need to estimate the notch width.
16346
- */
16347
- el.label !== undefined ||
16348
- getLabelSlot() === null) {
16349
- return false;
16350
- }
16351
- return true;
16352
- };
16353
- const calculateNotchWidth = () => {
16354
- if (needsExplicitNotchWidth()) {
16355
- /**
16356
- * Run this the frame after
16357
- * the browser has re-painted the host element.
16358
- * Otherwise, the label element may have a width
16359
- * of 0 and the IntersectionObserver will be used.
16360
- */
16361
- raf(() => {
16362
- setNotchWidth();
16363
- });
16364
- }
16365
- };
16366
- /**
16367
- * When using a label prop we can render
16368
- * the label value inside of the notch and
16369
- * let the browser calculate the size of the notch.
16370
- * However, we cannot render the label slot in multiple
16371
- * places so we need to manually calculate the notch dimension
16372
- * based on the size of the slotted content.
16373
- *
16374
- * This function should only be used to set the notch width
16375
- * on slotted label content. The notch width for label prop
16376
- * content is automatically calculated based on the
16377
- * intrinsic size of the label text.
16378
- */
16379
- const setNotchWidth = () => {
16380
- const notchSpacerEl = getNotchSpacerEl();
16381
- if (notchSpacerEl === undefined) {
16382
- return;
16383
- }
16384
- if (!needsExplicitNotchWidth()) {
16385
- notchSpacerEl.style.removeProperty('width');
16386
- return;
16387
- }
16388
- const width = getLabelSlot().scrollWidth;
16389
- if (
16390
- /**
16391
- * If the computed width of the label is 0
16392
- * and notchSpacerEl's offsetParent is null
16393
- * then that means the element is hidden.
16394
- * As a result, we need to wait for the element
16395
- * to become visible before setting the notch width.
16396
- *
16397
- * We do not check el.offsetParent because
16398
- * that can be null if the host element has
16399
- * position: fixed applied to it.
16400
- * notchSpacerEl does not have position: fixed.
16401
- */
16402
- width === 0 &&
16403
- notchSpacerEl.offsetParent === null &&
16404
- win$1 !== undefined &&
16405
- 'IntersectionObserver' in win$1) {
16406
- /**
16407
- * If there is an IO already attached
16408
- * then that will update the notch
16409
- * once the element becomes visible.
16410
- * As a result, there is no need to create
16411
- * another one.
16412
- */
16413
- if (notchVisibilityIO !== undefined) {
16414
- return;
16415
- }
16416
- const io = (notchVisibilityIO = new IntersectionObserver((ev) => {
16417
- /**
16418
- * If the element is visible then we
16419
- * can try setting the notch width again.
16420
- */
16421
- if (ev[0].intersectionRatio === 1) {
16422
- setNotchWidth();
16423
- io.disconnect();
16424
- notchVisibilityIO = undefined;
16425
- }
16426
- },
16427
- /**
16428
- * Set the root to be the host element
16429
- * This causes the IO callback
16430
- * to be fired in WebKit as soon as the element
16431
- * is visible. If we used the default root value
16432
- * then WebKit would only fire the IO callback
16433
- * after any animations (such as a modal transition)
16434
- * finished, and there would potentially be a flicker.
16435
- */
16436
- { threshold: 0.01, root: el }));
16437
- io.observe(notchSpacerEl);
16438
- return;
16439
- }
16440
- /**
16441
- * If the element is visible then we can set the notch width.
16442
- * The notch is only visible when the label is scaled,
16443
- * which is why we multiply the width by 0.75 as this is
16444
- * the same amount the label element is scaled by in the host CSS.
16445
- * (See $form-control-label-stacked-scale in ionic.globals.scss).
16446
- */
16447
- notchSpacerEl.style.setProperty('width', `${width * 0.75}px`);
16448
- };
16449
- const destroy = () => {
16450
- if (notchVisibilityIO) {
16451
- notchVisibilityIO.disconnect();
16452
- notchVisibilityIO = undefined;
16453
- }
16454
- };
16455
- return {
16456
- calculateNotchWidth,
16457
- destroy,
16458
- };
16459
- };
16460
-
16461
- /**
16462
- * Uses the compareWith param to compare two values to determine if they are equal.
16463
- *
16464
- * @param currentValue The current value of the control.
16465
- * @param compareValue The value to compare against.
16466
- * @param compareWith The function or property name to use to compare values.
16467
- */
16468
- const compareOptions = (currentValue, compareValue, compareWith) => {
16469
- if (typeof compareWith === 'function') {
16470
- return compareWith(currentValue, compareValue);
16471
- }
16472
- else if (typeof compareWith === 'string') {
16473
- return currentValue[compareWith] === compareValue[compareWith];
16474
- }
16475
- else {
16476
- return Array.isArray(compareValue) ? compareValue.includes(currentValue) : currentValue === compareValue;
16477
- }
16478
- };
16479
- /**
16480
- * Compares a value against the current value(s) to determine if it is selected.
16481
- *
16482
- * @param currentValue The current value of the control.
16483
- * @param compareValue The value to compare against.
16484
- * @param compareWith The function or property name to use to compare values.
16485
- */
16486
- const isOptionSelected = (currentValue, compareValue, compareWith) => {
16487
- if (currentValue === undefined) {
16488
- return false;
16489
- }
16490
- if (Array.isArray(currentValue)) {
16491
- return currentValue.some((val) => compareOptions(val, compareValue, compareWith));
16492
- }
16493
- else {
16494
- return compareOptions(currentValue, compareValue, compareWith);
16495
- }
16496
- };
16497
-
16498
- /**
16499
- * Checks if the form element is in an invalid state based on
16500
- * Ionic validation classes.
16501
- *
16502
- * @param el The form element to check.
16503
- * @returns `true` if the element is invalid, `false` otherwise.
16504
- */
16505
- const checkInvalidState = (el) => {
16506
- const hasIonTouched = el.classList.contains('ion-touched');
16507
- const hasIonInvalid = el.classList.contains('ion-invalid');
16508
- return hasIonTouched && hasIonInvalid;
16509
- };
16510
-
16511
16530
  /**
16512
16531
  * Used to update a scoped component that uses emulated slots. This fires when
16513
16532
  * content is passed into the slot or when the content inside of a slot changes.
@@ -28049,6 +28068,10 @@ class RadioGroup {
28049
28068
  this.helperTextId = `${this.inputId}-helper-text`;
28050
28069
  this.errorTextId = `${this.inputId}-error-text`;
28051
28070
  this.labelId = `${this.inputId}-lbl`;
28071
+ /**
28072
+ * Track validation state for proper aria-live announcements.
28073
+ */
28074
+ this.isInvalid = false;
28052
28075
  /**
28053
28076
  * If `true`, the radios can be deselected.
28054
28077
  */
@@ -28130,6 +28153,18 @@ class RadioGroup {
28130
28153
  this.labelId = label.id = this.name + '-lbl';
28131
28154
  }
28132
28155
  }
28156
+ // Always set initial state
28157
+ this.isInvalid = checkInvalidState(this.el);
28158
+ }
28159
+ componentWillLoad() {
28160
+ this.hintTextId = this.getHintTextId();
28161
+ }
28162
+ disconnectedCallback() {
28163
+ // Clean up validation observer to prevent memory leaks.
28164
+ if (this.validationObserver) {
28165
+ this.validationObserver.disconnect();
28166
+ this.validationObserver = undefined;
28167
+ }
28133
28168
  }
28134
28169
  getRadios() {
28135
28170
  return Array.from(this.el.querySelectorAll('ion-radio'));
@@ -28205,16 +28240,16 @@ class RadioGroup {
28205
28240
  * Renders the helper text or error text values
28206
28241
  */
28207
28242
  renderHintText() {
28208
- const { helperText, errorText, helperTextId, errorTextId } = this;
28243
+ const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
28209
28244
  const hasHintText = !!helperText || !!errorText;
28210
28245
  if (!hasHintText) {
28211
28246
  return;
28212
28247
  }
28213
- return (hAsync("div", { class: "radio-group-top" }, hAsync("div", { id: helperTextId, class: "helper-text" }, helperText), hAsync("div", { id: errorTextId, class: "error-text" }, errorText)));
28248
+ return (hAsync("div", { class: "radio-group-top" }, hAsync("div", { id: helperTextId, class: "helper-text", "aria-live": "polite" }, !isInvalid ? helperText : null), hAsync("div", { id: errorTextId, class: "error-text", role: "alert" }, isInvalid ? errorText : null)));
28214
28249
  }
28215
- getHintTextID() {
28216
- const { el, helperText, errorText, helperTextId, errorTextId } = this;
28217
- if (el.classList.contains('ion-touched') && el.classList.contains('ion-invalid') && errorText) {
28250
+ getHintTextId() {
28251
+ const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
28252
+ if (isInvalid && errorText) {
28218
28253
  return errorTextId;
28219
28254
  }
28220
28255
  if (helperText) {
@@ -28226,7 +28261,7 @@ class RadioGroup {
28226
28261
  const { label, labelId, el, name, value } = this;
28227
28262
  const mode = getIonMode$1(this);
28228
28263
  renderHiddenInput(true, el, name, value, false);
28229
- return (hAsync(Host, { key: '81b8ebc96b2f383c36717f290d2959cc921ad6e8', role: "radiogroup", "aria-labelledby": label ? labelId : null, "aria-describedby": this.getHintTextID(), "aria-invalid": this.getHintTextID() === this.errorTextId, onClick: this.onClick, class: mode }, this.renderHintText(), hAsync("div", { key: '45b09efc10776b889a8f372cba80d25a3fc849da', class: "radio-group-wrapper" }, hAsync("slot", { key: '58714934542c2fdd7396de160364f3f06b32e8f8' }))));
28264
+ return (hAsync(Host, { key: 'db593b3ed511e9395e3c7bfd91b787328692cd6d', role: "radiogroup", "aria-labelledby": label ? labelId : null, "aria-describedby": this.hintTextId, "aria-invalid": this.isInvalid ? 'true' : undefined, onClick: this.onClick, class: mode }, this.renderHintText(), hAsync("div", { key: '85045b45a0100a45f3b9a35d1c5a25ec63d525c4', class: "radio-group-wrapper" }, hAsync("slot", { key: '53dacb87ce62398e78771fb2efaf839ab922d946' }))));
28230
28265
  }
28231
28266
  get el() { return getElement(this); }
28232
28267
  static get watchers() { return {
@@ -28246,6 +28281,8 @@ class RadioGroup {
28246
28281
  "value": [1032],
28247
28282
  "helperText": [1, "helper-text"],
28248
28283
  "errorText": [1, "error-text"],
28284
+ "isInvalid": [32],
28285
+ "hintTextId": [32],
28249
28286
  "setFocus": [64]
28250
28287
  },
28251
28288
  "$listeners$": [[4, "keydown", "onKeydown"]],
@@ -33373,7 +33410,7 @@ class Select {
33373
33410
  }
33374
33411
  componentWillLoad() {
33375
33412
  this.inheritedAttributes = inheritAttributes$1(this.el, ['aria-label']);
33376
- this.hintTextID = this.getHintTextID();
33413
+ this.hintTextId = this.getHintTextId();
33377
33414
  }
33378
33415
  componentDidLoad() {
33379
33416
  /**
@@ -33872,9 +33909,9 @@ class Select {
33872
33909
  }
33873
33910
  renderListbox() {
33874
33911
  const { disabled, inputId, isExpanded, required } = this;
33875
- return (hAsync("button", { disabled: disabled, id: inputId, "aria-label": this.ariaLabel, "aria-haspopup": "dialog", "aria-expanded": `${isExpanded}`, "aria-describedby": this.hintTextID, "aria-invalid": this.isInvalid ? 'true' : undefined, "aria-required": `${required}`, onFocus: this.onFocus, onBlur: this.onBlur, ref: (focusEl) => (this.focusEl = focusEl) }));
33912
+ return (hAsync("button", { disabled: disabled, id: inputId, "aria-label": this.ariaLabel, "aria-haspopup": "dialog", "aria-expanded": `${isExpanded}`, "aria-describedby": this.hintTextId, "aria-invalid": this.isInvalid ? 'true' : undefined, "aria-required": `${required}`, onFocus: this.onFocus, onBlur: this.onBlur, ref: (focusEl) => (this.focusEl = focusEl) }));
33876
33913
  }
33877
- getHintTextID() {
33914
+ getHintTextId() {
33878
33915
  const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
33879
33916
  if (isInvalid && errorText) {
33880
33917
  return errorTextId;
@@ -33999,7 +34036,7 @@ class Select {
33999
34036
  "isExpanded": [32],
34000
34037
  "hasFocus": [32],
34001
34038
  "isInvalid": [32],
34002
- "hintTextID": [32],
34039
+ "hintTextId": [32],
34003
34040
  "open": [64]
34004
34041
  },
34005
34042
  "$listeners$": undefined,
@@ -36470,6 +36507,10 @@ class Toggle {
36470
36507
  this.inheritedAttributes = {};
36471
36508
  this.didLoad = false;
36472
36509
  this.activated = false;
36510
+ /**
36511
+ * Track validation state for proper aria-live announcements.
36512
+ */
36513
+ this.isInvalid = false;
36473
36514
  /**
36474
36515
  * The name of the control, which is submitted with the form data.
36475
36516
  */
@@ -36583,15 +36624,18 @@ class Toggle {
36583
36624
  });
36584
36625
  }
36585
36626
  async connectedCallback() {
36627
+ const { didLoad, el } = this;
36586
36628
  /**
36587
36629
  * If we have not yet rendered
36588
36630
  * ion-toggle, then toggleTrack is not defined.
36589
36631
  * But if we are moving ion-toggle via appendChild,
36590
36632
  * then toggleTrack will be defined.
36591
36633
  */
36592
- if (this.didLoad) {
36634
+ if (didLoad) {
36593
36635
  this.setupGesture();
36594
36636
  }
36637
+ // Always set initial state
36638
+ this.isInvalid = checkInvalidState(el);
36595
36639
  }
36596
36640
  componentDidLoad() {
36597
36641
  this.setupGesture();
@@ -36602,9 +36646,15 @@ class Toggle {
36602
36646
  this.gesture.destroy();
36603
36647
  this.gesture = undefined;
36604
36648
  }
36649
+ // Clean up validation observer to prevent memory leaks.
36650
+ if (this.validationObserver) {
36651
+ this.validationObserver.disconnect();
36652
+ this.validationObserver = undefined;
36653
+ }
36605
36654
  }
36606
36655
  componentWillLoad() {
36607
36656
  this.inheritedAttributes = Object.assign({}, inheritAriaAttributes(this.el));
36657
+ this.hintTextId = this.getHintTextId();
36608
36658
  }
36609
36659
  onStart() {
36610
36660
  this.activated = true;
@@ -36645,9 +36695,9 @@ class Toggle {
36645
36695
  get hasLabel() {
36646
36696
  return this.el.textContent !== '';
36647
36697
  }
36648
- getHintTextID() {
36649
- const { el, helperText, errorText, helperTextId, errorTextId } = this;
36650
- if (el.classList.contains('ion-touched') && el.classList.contains('ion-invalid') && errorText) {
36698
+ getHintTextId() {
36699
+ const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
36700
+ if (isInvalid && errorText) {
36651
36701
  return errorTextId;
36652
36702
  }
36653
36703
  if (helperText) {
@@ -36660,7 +36710,7 @@ class Toggle {
36660
36710
  * This element should only be rendered if hint text is set.
36661
36711
  */
36662
36712
  renderHintText() {
36663
- const { helperText, errorText, helperTextId, errorTextId } = this;
36713
+ const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
36664
36714
  /**
36665
36715
  * undefined and empty string values should
36666
36716
  * be treated as not having helper/error text.
@@ -36669,15 +36719,15 @@ class Toggle {
36669
36719
  if (!hasHintText) {
36670
36720
  return;
36671
36721
  }
36672
- return (hAsync("div", { class: "toggle-bottom" }, hAsync("div", { id: helperTextId, class: "helper-text", part: "supporting-text helper-text" }, helperText), hAsync("div", { id: errorTextId, class: "error-text", part: "supporting-text error-text" }, errorText)));
36722
+ return (hAsync("div", { class: "toggle-bottom" }, hAsync("div", { id: helperTextId, class: "helper-text", part: "supporting-text helper-text", "aria-live": "polite" }, !isInvalid ? helperText : null), hAsync("div", { id: errorTextId, class: "error-text", part: "supporting-text error-text", role: "alert" }, isInvalid ? errorText : null)));
36673
36723
  }
36674
36724
  render() {
36675
- const { activated, alignment, checked, color, disabled, el, errorTextId, hasLabel, inheritedAttributes, inputId, inputLabelId, justify, labelPlacement, name, required, } = this;
36725
+ const { activated, alignment, checked, color, disabled, el, hasLabel, inheritedAttributes, inputId, inputLabelId, justify, labelPlacement, name, required, } = this;
36676
36726
  const mode = getIonMode$1(this);
36677
36727
  const value = this.getValue();
36678
36728
  const rtl = isRTL$1(el) ? 'rtl' : 'ltr';
36679
36729
  renderHiddenInput(true, el, name, checked ? value : '', disabled);
36680
- return (hAsync(Host, { key: '17bbbc8d229868e5c872b2bc5a3faf579780c5e0', role: "switch", "aria-checked": `${checked}`, "aria-describedby": this.getHintTextID(), "aria-invalid": this.getHintTextID() === errorTextId, onClick: this.onClick, "aria-labelledby": hasLabel ? inputLabelId : null, "aria-label": inheritedAttributes['aria-label'] || null, "aria-disabled": disabled ? 'true' : null, tabindex: disabled ? undefined : 0, onKeyDown: this.onKeyDown, onFocus: this.onFocus, onBlur: this.onBlur, class: createColorClasses$1(color, {
36730
+ return (hAsync(Host, { key: 'f569148edd89ee041a4719ffc4733c16b05229bd', role: "switch", "aria-checked": `${checked}`, "aria-describedby": this.hintTextId, "aria-invalid": this.isInvalid ? 'true' : undefined, onClick: this.onClick, "aria-labelledby": hasLabel ? inputLabelId : null, "aria-label": inheritedAttributes['aria-label'] || null, "aria-disabled": disabled ? 'true' : null, "aria-required": required ? 'true' : undefined, tabindex: disabled ? undefined : 0, onKeyDown: this.onKeyDown, onFocus: this.onFocus, onBlur: this.onBlur, class: createColorClasses$1(color, {
36681
36731
  [mode]: true,
36682
36732
  'in-item': hostContext('ion-item', el),
36683
36733
  'toggle-activated': activated,
@@ -36687,10 +36737,10 @@ class Toggle {
36687
36737
  [`toggle-alignment-${alignment}`]: alignment !== undefined,
36688
36738
  [`toggle-label-placement-${labelPlacement}`]: true,
36689
36739
  [`toggle-${rtl}`]: true,
36690
- }) }, hAsync("label", { key: '673625b62a2c909e95dccb642c91312967a6cd1c', class: "toggle-wrapper", htmlFor: inputId }, hAsync("input", Object.assign({ key: '7dc3f357b4708116663970047765da9f8f845bf0', type: "checkbox", role: "switch", "aria-checked": `${checked}`, checked: checked, disabled: disabled, id: inputId, required: required }, inheritedAttributes)), hAsync("div", { key: '8f1c6a182031e8cbc6727e5f4ac0e00ad4247447', class: {
36740
+ }) }, hAsync("label", { key: '3027f2ac4be6de422a14486d847fbee77f615db1', class: "toggle-wrapper", htmlFor: inputId }, hAsync("input", Object.assign({ key: '4b0304c9e879e432b80184b4e5de37d55c11b436', type: "checkbox", role: "switch", "aria-checked": `${checked}`, checked: checked, disabled: disabled, id: inputId, required: required }, inheritedAttributes)), hAsync("div", { key: '8ef265ec942e7f01ff31cbb202ed146c6bf94e02', class: {
36691
36741
  'label-text-wrapper': true,
36692
36742
  'label-text-wrapper-hidden': !hasLabel,
36693
- }, part: "label", id: inputLabelId, onClick: this.onDivLabelClick }, hAsync("slot", { key: '8322b9d54dc7edeb4e16fefcde9f7ebca8d5c3e1' }), this.renderHintText()), hAsync("div", { key: 'fe6984143db817a7b3020a3f57cf5418fc3dcc0e', class: "native-wrapper" }, this.renderToggleControl()))));
36743
+ }, part: "label", id: inputLabelId, onClick: this.onDivLabelClick }, hAsync("slot", { key: '7b162b7dd27199cca2a4c995276a18b9f8e44aaf' }), this.renderHintText()), hAsync("div", { key: 'd13c34bd42fca01cc73ddb4ea7e471b33a282a3e', class: "native-wrapper" }, this.renderToggleControl()))));
36694
36744
  }
36695
36745
  get el() { return getElement(this); }
36696
36746
  static get watchers() { return {
@@ -36716,7 +36766,9 @@ class Toggle {
36716
36766
  "justify": [1],
36717
36767
  "alignment": [1],
36718
36768
  "required": [4],
36719
- "activated": [32]
36769
+ "activated": [32],
36770
+ "isInvalid": [32],
36771
+ "hintTextId": [32]
36720
36772
  },
36721
36773
  "$listeners$": undefined,
36722
36774
  "$lazyBundleId$": "-",