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

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.js CHANGED
@@ -9685,6 +9685,202 @@ class CardTitle {
9685
9685
  }; }
9686
9686
  }
9687
9687
 
9688
+ /**
9689
+ * A utility to calculate the size of an outline notch
9690
+ * width relative to the content passed. This is used in
9691
+ * components such as `ion-select` with `fill="outline"`
9692
+ * where we need to pass slotted HTML content. This is not
9693
+ * needed when rendering plaintext content because we can
9694
+ * render the plaintext again hidden with `opacity: 0` inside
9695
+ * of the notch. As a result we can rely on the intrinsic size
9696
+ * of the element to correctly compute the notch width. We
9697
+ * cannot do this with slotted content because we cannot project
9698
+ * it into 2 places at once.
9699
+ *
9700
+ * @internal
9701
+ * @param el: The host element
9702
+ * @param getNotchSpacerEl: A function that returns a reference to the notch spacer element inside of the component template.
9703
+ * @param getLabelSlot: A function that returns a reference to the slotted content.
9704
+ */
9705
+ const createNotchController = (el, getNotchSpacerEl, getLabelSlot) => {
9706
+ let notchVisibilityIO;
9707
+ const needsExplicitNotchWidth = () => {
9708
+ const notchSpacerEl = getNotchSpacerEl();
9709
+ if (
9710
+ /**
9711
+ * If the notch is not being used
9712
+ * then we do not need to set the notch width.
9713
+ */
9714
+ notchSpacerEl === undefined ||
9715
+ /**
9716
+ * If either the label property is being
9717
+ * used or the label slot is not defined,
9718
+ * then we do not need to estimate the notch width.
9719
+ */
9720
+ el.label !== undefined ||
9721
+ getLabelSlot() === null) {
9722
+ return false;
9723
+ }
9724
+ return true;
9725
+ };
9726
+ const calculateNotchWidth = () => {
9727
+ if (needsExplicitNotchWidth()) {
9728
+ /**
9729
+ * Run this the frame after
9730
+ * the browser has re-painted the host element.
9731
+ * Otherwise, the label element may have a width
9732
+ * of 0 and the IntersectionObserver will be used.
9733
+ */
9734
+ raf(() => {
9735
+ setNotchWidth();
9736
+ });
9737
+ }
9738
+ };
9739
+ /**
9740
+ * When using a label prop we can render
9741
+ * the label value inside of the notch and
9742
+ * let the browser calculate the size of the notch.
9743
+ * However, we cannot render the label slot in multiple
9744
+ * places so we need to manually calculate the notch dimension
9745
+ * based on the size of the slotted content.
9746
+ *
9747
+ * This function should only be used to set the notch width
9748
+ * on slotted label content. The notch width for label prop
9749
+ * content is automatically calculated based on the
9750
+ * intrinsic size of the label text.
9751
+ */
9752
+ const setNotchWidth = () => {
9753
+ const notchSpacerEl = getNotchSpacerEl();
9754
+ if (notchSpacerEl === undefined) {
9755
+ return;
9756
+ }
9757
+ if (!needsExplicitNotchWidth()) {
9758
+ notchSpacerEl.style.removeProperty('width');
9759
+ return;
9760
+ }
9761
+ const width = getLabelSlot().scrollWidth;
9762
+ if (
9763
+ /**
9764
+ * If the computed width of the label is 0
9765
+ * and notchSpacerEl's offsetParent is null
9766
+ * then that means the element is hidden.
9767
+ * As a result, we need to wait for the element
9768
+ * to become visible before setting the notch width.
9769
+ *
9770
+ * We do not check el.offsetParent because
9771
+ * that can be null if the host element has
9772
+ * position: fixed applied to it.
9773
+ * notchSpacerEl does not have position: fixed.
9774
+ */
9775
+ width === 0 &&
9776
+ notchSpacerEl.offsetParent === null &&
9777
+ win$1 !== undefined &&
9778
+ 'IntersectionObserver' in win$1) {
9779
+ /**
9780
+ * If there is an IO already attached
9781
+ * then that will update the notch
9782
+ * once the element becomes visible.
9783
+ * As a result, there is no need to create
9784
+ * another one.
9785
+ */
9786
+ if (notchVisibilityIO !== undefined) {
9787
+ return;
9788
+ }
9789
+ const io = (notchVisibilityIO = new IntersectionObserver((ev) => {
9790
+ /**
9791
+ * If the element is visible then we
9792
+ * can try setting the notch width again.
9793
+ */
9794
+ if (ev[0].intersectionRatio === 1) {
9795
+ setNotchWidth();
9796
+ io.disconnect();
9797
+ notchVisibilityIO = undefined;
9798
+ }
9799
+ },
9800
+ /**
9801
+ * Set the root to be the host element
9802
+ * This causes the IO callback
9803
+ * to be fired in WebKit as soon as the element
9804
+ * is visible. If we used the default root value
9805
+ * then WebKit would only fire the IO callback
9806
+ * after any animations (such as a modal transition)
9807
+ * finished, and there would potentially be a flicker.
9808
+ */
9809
+ { threshold: 0.01, root: el }));
9810
+ io.observe(notchSpacerEl);
9811
+ return;
9812
+ }
9813
+ /**
9814
+ * If the element is visible then we can set the notch width.
9815
+ * The notch is only visible when the label is scaled,
9816
+ * which is why we multiply the width by 0.75 as this is
9817
+ * the same amount the label element is scaled by in the host CSS.
9818
+ * (See $form-control-label-stacked-scale in ionic.globals.scss).
9819
+ */
9820
+ notchSpacerEl.style.setProperty('width', `${width * 0.75}px`);
9821
+ };
9822
+ const destroy = () => {
9823
+ if (notchVisibilityIO) {
9824
+ notchVisibilityIO.disconnect();
9825
+ notchVisibilityIO = undefined;
9826
+ }
9827
+ };
9828
+ return {
9829
+ calculateNotchWidth,
9830
+ destroy,
9831
+ };
9832
+ };
9833
+
9834
+ /**
9835
+ * Uses the compareWith param to compare two values to determine if they are equal.
9836
+ *
9837
+ * @param currentValue The current value of the control.
9838
+ * @param compareValue The value to compare against.
9839
+ * @param compareWith The function or property name to use to compare values.
9840
+ */
9841
+ const compareOptions = (currentValue, compareValue, compareWith) => {
9842
+ if (typeof compareWith === 'function') {
9843
+ return compareWith(currentValue, compareValue);
9844
+ }
9845
+ else if (typeof compareWith === 'string') {
9846
+ return currentValue[compareWith] === compareValue[compareWith];
9847
+ }
9848
+ else {
9849
+ return Array.isArray(compareValue) ? compareValue.includes(currentValue) : currentValue === compareValue;
9850
+ }
9851
+ };
9852
+ /**
9853
+ * Compares a value against the current value(s) to determine if it is selected.
9854
+ *
9855
+ * @param currentValue The current value of the control.
9856
+ * @param compareValue The value to compare against.
9857
+ * @param compareWith The function or property name to use to compare values.
9858
+ */
9859
+ const isOptionSelected = (currentValue, compareValue, compareWith) => {
9860
+ if (currentValue === undefined) {
9861
+ return false;
9862
+ }
9863
+ if (Array.isArray(currentValue)) {
9864
+ return currentValue.some((val) => compareOptions(val, compareValue, compareWith));
9865
+ }
9866
+ else {
9867
+ return compareOptions(currentValue, compareValue, compareWith);
9868
+ }
9869
+ };
9870
+
9871
+ /**
9872
+ * Checks if the form element is in an invalid state based on
9873
+ * Ionic validation classes.
9874
+ *
9875
+ * @param el The form element to check.
9876
+ * @returns `true` if the element is invalid, `false` otherwise.
9877
+ */
9878
+ const checkInvalidState = (el) => {
9879
+ const hasIonTouched = el.classList.contains('ion-touched');
9880
+ const hasIonInvalid = el.classList.contains('ion-invalid');
9881
+ return hasIonTouched && hasIonInvalid;
9882
+ };
9883
+
9688
9884
  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}";
9689
9885
 
9690
9886
  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}";
@@ -9750,6 +9946,10 @@ class Checkbox {
9750
9946
  * submitting if the value is invalid.
9751
9947
  */
9752
9948
  this.required = false;
9949
+ /**
9950
+ * Track validation state for proper aria-live announcements.
9951
+ */
9952
+ this.isInvalid = false;
9753
9953
  /**
9754
9954
  * Sets the checked property and emits
9755
9955
  * the ionChange event. Use this to update the
@@ -9796,16 +9996,29 @@ class Checkbox {
9796
9996
  ev.stopPropagation();
9797
9997
  };
9798
9998
  }
9999
+ connectedCallback() {
10000
+ const { el } = this;
10001
+ // Always set initial state
10002
+ this.isInvalid = checkInvalidState(el);
10003
+ }
9799
10004
  componentWillLoad() {
9800
10005
  this.inheritedAttributes = Object.assign({}, inheritAriaAttributes(this.el));
10006
+ this.hintTextId = this.getHintTextId();
10007
+ }
10008
+ disconnectedCallback() {
10009
+ // Clean up validation observer to prevent memory leaks.
10010
+ if (this.validationObserver) {
10011
+ this.validationObserver.disconnect();
10012
+ this.validationObserver = undefined;
10013
+ }
9801
10014
  }
9802
10015
  /** @internal */
9803
10016
  async setFocus() {
9804
10017
  this.el.focus();
9805
10018
  }
9806
- getHintTextID() {
9807
- const { el, helperText, errorText, helperTextId, errorTextId } = this;
9808
- if (el.classList.contains('ion-touched') && el.classList.contains('ion-invalid') && errorText) {
10019
+ getHintTextId() {
10020
+ const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
10021
+ if (isInvalid && errorText) {
9809
10022
  return errorTextId;
9810
10023
  }
9811
10024
  if (helperText) {
@@ -9818,7 +10031,7 @@ class Checkbox {
9818
10031
  * This element should only be rendered if hint text is set.
9819
10032
  */
9820
10033
  renderHintText() {
9821
- const { helperText, errorText, helperTextId, errorTextId } = this;
10034
+ const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
9822
10035
  /**
9823
10036
  * undefined and empty string values should
9824
10037
  * be treated as not having helper/error text.
@@ -9827,7 +10040,7 @@ class Checkbox {
9827
10040
  if (!hasHintText) {
9828
10041
  return;
9829
10042
  }
9830
- 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)));
10043
+ 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)));
9831
10044
  }
9832
10045
  render() {
9833
10046
  const { color, checked, disabled, el, getSVGPath, indeterminate, inheritedAttributes, inputId, justify, labelPlacement, name, value, alignment, required, } = this;
@@ -9837,7 +10050,7 @@ class Checkbox {
9837
10050
  renderHiddenInput(true, el, name, checked ? value : '', disabled);
9838
10051
  // The host element must have a checkbox role to ensure proper VoiceOver
9839
10052
  // support in Safari for accessibility.
9840
- 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, {
10053
+ 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, {
9841
10054
  [mode]: true,
9842
10055
  'in-item': hostContext('ion-item', el),
9843
10056
  'checkbox-checked': checked,
@@ -9847,10 +10060,10 @@ class Checkbox {
9847
10060
  [`checkbox-justify-${justify}`]: justify !== undefined,
9848
10061
  [`checkbox-alignment-${alignment}`]: alignment !== undefined,
9849
10062
  [`checkbox-label-placement-${labelPlacement}`]: true,
9850
- }) }, 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: {
10063
+ }) }, 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: {
9851
10064
  'label-text-wrapper': true,
9852
10065
  'label-text-wrapper-hidden': !hasLabelContent,
9853
- }, 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)))));
10066
+ }, 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)))));
9854
10067
  }
9855
10068
  getSVGPath(mode, indeterminate) {
9856
10069
  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" }));
@@ -9880,6 +10093,8 @@ class Checkbox {
9880
10093
  "justify": [1],
9881
10094
  "alignment": [1],
9882
10095
  "required": [4],
10096
+ "isInvalid": [32],
10097
+ "hintTextId": [32],
9883
10098
  "setFocus": [64]
9884
10099
  },
9885
10100
  "$listeners$": undefined,
@@ -16314,202 +16529,6 @@ class InfiniteScrollContent {
16314
16529
  }; }
16315
16530
  }
16316
16531
 
16317
- /**
16318
- * A utility to calculate the size of an outline notch
16319
- * width relative to the content passed. This is used in
16320
- * components such as `ion-select` with `fill="outline"`
16321
- * where we need to pass slotted HTML content. This is not
16322
- * needed when rendering plaintext content because we can
16323
- * render the plaintext again hidden with `opacity: 0` inside
16324
- * of the notch. As a result we can rely on the intrinsic size
16325
- * of the element to correctly compute the notch width. We
16326
- * cannot do this with slotted content because we cannot project
16327
- * it into 2 places at once.
16328
- *
16329
- * @internal
16330
- * @param el: The host element
16331
- * @param getNotchSpacerEl: A function that returns a reference to the notch spacer element inside of the component template.
16332
- * @param getLabelSlot: A function that returns a reference to the slotted content.
16333
- */
16334
- const createNotchController = (el, getNotchSpacerEl, getLabelSlot) => {
16335
- let notchVisibilityIO;
16336
- const needsExplicitNotchWidth = () => {
16337
- const notchSpacerEl = getNotchSpacerEl();
16338
- if (
16339
- /**
16340
- * If the notch is not being used
16341
- * then we do not need to set the notch width.
16342
- */
16343
- notchSpacerEl === undefined ||
16344
- /**
16345
- * If either the label property is being
16346
- * used or the label slot is not defined,
16347
- * then we do not need to estimate the notch width.
16348
- */
16349
- el.label !== undefined ||
16350
- getLabelSlot() === null) {
16351
- return false;
16352
- }
16353
- return true;
16354
- };
16355
- const calculateNotchWidth = () => {
16356
- if (needsExplicitNotchWidth()) {
16357
- /**
16358
- * Run this the frame after
16359
- * the browser has re-painted the host element.
16360
- * Otherwise, the label element may have a width
16361
- * of 0 and the IntersectionObserver will be used.
16362
- */
16363
- raf(() => {
16364
- setNotchWidth();
16365
- });
16366
- }
16367
- };
16368
- /**
16369
- * When using a label prop we can render
16370
- * the label value inside of the notch and
16371
- * let the browser calculate the size of the notch.
16372
- * However, we cannot render the label slot in multiple
16373
- * places so we need to manually calculate the notch dimension
16374
- * based on the size of the slotted content.
16375
- *
16376
- * This function should only be used to set the notch width
16377
- * on slotted label content. The notch width for label prop
16378
- * content is automatically calculated based on the
16379
- * intrinsic size of the label text.
16380
- */
16381
- const setNotchWidth = () => {
16382
- const notchSpacerEl = getNotchSpacerEl();
16383
- if (notchSpacerEl === undefined) {
16384
- return;
16385
- }
16386
- if (!needsExplicitNotchWidth()) {
16387
- notchSpacerEl.style.removeProperty('width');
16388
- return;
16389
- }
16390
- const width = getLabelSlot().scrollWidth;
16391
- if (
16392
- /**
16393
- * If the computed width of the label is 0
16394
- * and notchSpacerEl's offsetParent is null
16395
- * then that means the element is hidden.
16396
- * As a result, we need to wait for the element
16397
- * to become visible before setting the notch width.
16398
- *
16399
- * We do not check el.offsetParent because
16400
- * that can be null if the host element has
16401
- * position: fixed applied to it.
16402
- * notchSpacerEl does not have position: fixed.
16403
- */
16404
- width === 0 &&
16405
- notchSpacerEl.offsetParent === null &&
16406
- win$1 !== undefined &&
16407
- 'IntersectionObserver' in win$1) {
16408
- /**
16409
- * If there is an IO already attached
16410
- * then that will update the notch
16411
- * once the element becomes visible.
16412
- * As a result, there is no need to create
16413
- * another one.
16414
- */
16415
- if (notchVisibilityIO !== undefined) {
16416
- return;
16417
- }
16418
- const io = (notchVisibilityIO = new IntersectionObserver((ev) => {
16419
- /**
16420
- * If the element is visible then we
16421
- * can try setting the notch width again.
16422
- */
16423
- if (ev[0].intersectionRatio === 1) {
16424
- setNotchWidth();
16425
- io.disconnect();
16426
- notchVisibilityIO = undefined;
16427
- }
16428
- },
16429
- /**
16430
- * Set the root to be the host element
16431
- * This causes the IO callback
16432
- * to be fired in WebKit as soon as the element
16433
- * is visible. If we used the default root value
16434
- * then WebKit would only fire the IO callback
16435
- * after any animations (such as a modal transition)
16436
- * finished, and there would potentially be a flicker.
16437
- */
16438
- { threshold: 0.01, root: el }));
16439
- io.observe(notchSpacerEl);
16440
- return;
16441
- }
16442
- /**
16443
- * If the element is visible then we can set the notch width.
16444
- * The notch is only visible when the label is scaled,
16445
- * which is why we multiply the width by 0.75 as this is
16446
- * the same amount the label element is scaled by in the host CSS.
16447
- * (See $form-control-label-stacked-scale in ionic.globals.scss).
16448
- */
16449
- notchSpacerEl.style.setProperty('width', `${width * 0.75}px`);
16450
- };
16451
- const destroy = () => {
16452
- if (notchVisibilityIO) {
16453
- notchVisibilityIO.disconnect();
16454
- notchVisibilityIO = undefined;
16455
- }
16456
- };
16457
- return {
16458
- calculateNotchWidth,
16459
- destroy,
16460
- };
16461
- };
16462
-
16463
- /**
16464
- * Uses the compareWith param to compare two values to determine if they are equal.
16465
- *
16466
- * @param currentValue The current value of the control.
16467
- * @param compareValue The value to compare against.
16468
- * @param compareWith The function or property name to use to compare values.
16469
- */
16470
- const compareOptions = (currentValue, compareValue, compareWith) => {
16471
- if (typeof compareWith === 'function') {
16472
- return compareWith(currentValue, compareValue);
16473
- }
16474
- else if (typeof compareWith === 'string') {
16475
- return currentValue[compareWith] === compareValue[compareWith];
16476
- }
16477
- else {
16478
- return Array.isArray(compareValue) ? compareValue.includes(currentValue) : currentValue === compareValue;
16479
- }
16480
- };
16481
- /**
16482
- * Compares a value against the current value(s) to determine if it is selected.
16483
- *
16484
- * @param currentValue The current value of the control.
16485
- * @param compareValue The value to compare against.
16486
- * @param compareWith The function or property name to use to compare values.
16487
- */
16488
- const isOptionSelected = (currentValue, compareValue, compareWith) => {
16489
- if (currentValue === undefined) {
16490
- return false;
16491
- }
16492
- if (Array.isArray(currentValue)) {
16493
- return currentValue.some((val) => compareOptions(val, compareValue, compareWith));
16494
- }
16495
- else {
16496
- return compareOptions(currentValue, compareValue, compareWith);
16497
- }
16498
- };
16499
-
16500
- /**
16501
- * Checks if the form element is in an invalid state based on
16502
- * Ionic validation classes.
16503
- *
16504
- * @param el The form element to check.
16505
- * @returns `true` if the element is invalid, `false` otherwise.
16506
- */
16507
- const checkInvalidState = (el) => {
16508
- const hasIonTouched = el.classList.contains('ion-touched');
16509
- const hasIonInvalid = el.classList.contains('ion-invalid');
16510
- return hasIonTouched && hasIonInvalid;
16511
- };
16512
-
16513
16532
  /**
16514
16533
  * Used to update a scoped component that uses emulated slots. This fires when
16515
16534
  * content is passed into the slot or when the content inside of a slot changes.
@@ -28051,6 +28070,10 @@ class RadioGroup {
28051
28070
  this.helperTextId = `${this.inputId}-helper-text`;
28052
28071
  this.errorTextId = `${this.inputId}-error-text`;
28053
28072
  this.labelId = `${this.inputId}-lbl`;
28073
+ /**
28074
+ * Track validation state for proper aria-live announcements.
28075
+ */
28076
+ this.isInvalid = false;
28054
28077
  /**
28055
28078
  * If `true`, the radios can be deselected.
28056
28079
  */
@@ -28132,6 +28155,18 @@ class RadioGroup {
28132
28155
  this.labelId = label.id = this.name + '-lbl';
28133
28156
  }
28134
28157
  }
28158
+ // Always set initial state
28159
+ this.isInvalid = checkInvalidState(this.el);
28160
+ }
28161
+ componentWillLoad() {
28162
+ this.hintTextId = this.getHintTextId();
28163
+ }
28164
+ disconnectedCallback() {
28165
+ // Clean up validation observer to prevent memory leaks.
28166
+ if (this.validationObserver) {
28167
+ this.validationObserver.disconnect();
28168
+ this.validationObserver = undefined;
28169
+ }
28135
28170
  }
28136
28171
  getRadios() {
28137
28172
  return Array.from(this.el.querySelectorAll('ion-radio'));
@@ -28207,16 +28242,16 @@ class RadioGroup {
28207
28242
  * Renders the helper text or error text values
28208
28243
  */
28209
28244
  renderHintText() {
28210
- const { helperText, errorText, helperTextId, errorTextId } = this;
28245
+ const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
28211
28246
  const hasHintText = !!helperText || !!errorText;
28212
28247
  if (!hasHintText) {
28213
28248
  return;
28214
28249
  }
28215
- return (hAsync("div", { class: "radio-group-top" }, hAsync("div", { id: helperTextId, class: "helper-text" }, helperText), hAsync("div", { id: errorTextId, class: "error-text" }, errorText)));
28250
+ 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)));
28216
28251
  }
28217
- getHintTextID() {
28218
- const { el, helperText, errorText, helperTextId, errorTextId } = this;
28219
- if (el.classList.contains('ion-touched') && el.classList.contains('ion-invalid') && errorText) {
28252
+ getHintTextId() {
28253
+ const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
28254
+ if (isInvalid && errorText) {
28220
28255
  return errorTextId;
28221
28256
  }
28222
28257
  if (helperText) {
@@ -28228,7 +28263,7 @@ class RadioGroup {
28228
28263
  const { label, labelId, el, name, value } = this;
28229
28264
  const mode = getIonMode$1(this);
28230
28265
  renderHiddenInput(true, el, name, value, false);
28231
- 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' }))));
28266
+ 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' }))));
28232
28267
  }
28233
28268
  get el() { return getElement(this); }
28234
28269
  static get watchers() { return {
@@ -28248,6 +28283,8 @@ class RadioGroup {
28248
28283
  "value": [1032],
28249
28284
  "helperText": [1, "helper-text"],
28250
28285
  "errorText": [1, "error-text"],
28286
+ "isInvalid": [32],
28287
+ "hintTextId": [32],
28251
28288
  "setFocus": [64]
28252
28289
  },
28253
28290
  "$listeners$": [[4, "keydown", "onKeydown"]],
@@ -33375,7 +33412,7 @@ class Select {
33375
33412
  }
33376
33413
  componentWillLoad() {
33377
33414
  this.inheritedAttributes = inheritAttributes$1(this.el, ['aria-label']);
33378
- this.hintTextID = this.getHintTextID();
33415
+ this.hintTextId = this.getHintTextId();
33379
33416
  }
33380
33417
  componentDidLoad() {
33381
33418
  /**
@@ -33874,9 +33911,9 @@ class Select {
33874
33911
  }
33875
33912
  renderListbox() {
33876
33913
  const { disabled, inputId, isExpanded, required } = this;
33877
- 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) }));
33914
+ 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) }));
33878
33915
  }
33879
- getHintTextID() {
33916
+ getHintTextId() {
33880
33917
  const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
33881
33918
  if (isInvalid && errorText) {
33882
33919
  return errorTextId;
@@ -34001,7 +34038,7 @@ class Select {
34001
34038
  "isExpanded": [32],
34002
34039
  "hasFocus": [32],
34003
34040
  "isInvalid": [32],
34004
- "hintTextID": [32],
34041
+ "hintTextId": [32],
34005
34042
  "open": [64]
34006
34043
  },
34007
34044
  "$listeners$": undefined,
@@ -36472,6 +36509,10 @@ class Toggle {
36472
36509
  this.inheritedAttributes = {};
36473
36510
  this.didLoad = false;
36474
36511
  this.activated = false;
36512
+ /**
36513
+ * Track validation state for proper aria-live announcements.
36514
+ */
36515
+ this.isInvalid = false;
36475
36516
  /**
36476
36517
  * The name of the control, which is submitted with the form data.
36477
36518
  */
@@ -36585,15 +36626,18 @@ class Toggle {
36585
36626
  });
36586
36627
  }
36587
36628
  async connectedCallback() {
36629
+ const { didLoad, el } = this;
36588
36630
  /**
36589
36631
  * If we have not yet rendered
36590
36632
  * ion-toggle, then toggleTrack is not defined.
36591
36633
  * But if we are moving ion-toggle via appendChild,
36592
36634
  * then toggleTrack will be defined.
36593
36635
  */
36594
- if (this.didLoad) {
36636
+ if (didLoad) {
36595
36637
  this.setupGesture();
36596
36638
  }
36639
+ // Always set initial state
36640
+ this.isInvalid = checkInvalidState(el);
36597
36641
  }
36598
36642
  componentDidLoad() {
36599
36643
  this.setupGesture();
@@ -36604,9 +36648,15 @@ class Toggle {
36604
36648
  this.gesture.destroy();
36605
36649
  this.gesture = undefined;
36606
36650
  }
36651
+ // Clean up validation observer to prevent memory leaks.
36652
+ if (this.validationObserver) {
36653
+ this.validationObserver.disconnect();
36654
+ this.validationObserver = undefined;
36655
+ }
36607
36656
  }
36608
36657
  componentWillLoad() {
36609
36658
  this.inheritedAttributes = Object.assign({}, inheritAriaAttributes(this.el));
36659
+ this.hintTextId = this.getHintTextId();
36610
36660
  }
36611
36661
  onStart() {
36612
36662
  this.activated = true;
@@ -36647,9 +36697,9 @@ class Toggle {
36647
36697
  get hasLabel() {
36648
36698
  return this.el.textContent !== '';
36649
36699
  }
36650
- getHintTextID() {
36651
- const { el, helperText, errorText, helperTextId, errorTextId } = this;
36652
- if (el.classList.contains('ion-touched') && el.classList.contains('ion-invalid') && errorText) {
36700
+ getHintTextId() {
36701
+ const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
36702
+ if (isInvalid && errorText) {
36653
36703
  return errorTextId;
36654
36704
  }
36655
36705
  if (helperText) {
@@ -36662,7 +36712,7 @@ class Toggle {
36662
36712
  * This element should only be rendered if hint text is set.
36663
36713
  */
36664
36714
  renderHintText() {
36665
- const { helperText, errorText, helperTextId, errorTextId } = this;
36715
+ const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
36666
36716
  /**
36667
36717
  * undefined and empty string values should
36668
36718
  * be treated as not having helper/error text.
@@ -36671,15 +36721,15 @@ class Toggle {
36671
36721
  if (!hasHintText) {
36672
36722
  return;
36673
36723
  }
36674
- 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)));
36724
+ 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)));
36675
36725
  }
36676
36726
  render() {
36677
- const { activated, alignment, checked, color, disabled, el, errorTextId, hasLabel, inheritedAttributes, inputId, inputLabelId, justify, labelPlacement, name, required, } = this;
36727
+ const { activated, alignment, checked, color, disabled, el, hasLabel, inheritedAttributes, inputId, inputLabelId, justify, labelPlacement, name, required, } = this;
36678
36728
  const mode = getIonMode$1(this);
36679
36729
  const value = this.getValue();
36680
36730
  const rtl = isRTL$1(el) ? 'rtl' : 'ltr';
36681
36731
  renderHiddenInput(true, el, name, checked ? value : '', disabled);
36682
- 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, {
36732
+ 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, {
36683
36733
  [mode]: true,
36684
36734
  'in-item': hostContext('ion-item', el),
36685
36735
  'toggle-activated': activated,
@@ -36689,10 +36739,10 @@ class Toggle {
36689
36739
  [`toggle-alignment-${alignment}`]: alignment !== undefined,
36690
36740
  [`toggle-label-placement-${labelPlacement}`]: true,
36691
36741
  [`toggle-${rtl}`]: true,
36692
- }) }, 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: {
36742
+ }) }, 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: {
36693
36743
  'label-text-wrapper': true,
36694
36744
  'label-text-wrapper-hidden': !hasLabel,
36695
- }, part: "label", id: inputLabelId, onClick: this.onDivLabelClick }, hAsync("slot", { key: '8322b9d54dc7edeb4e16fefcde9f7ebca8d5c3e1' }), this.renderHintText()), hAsync("div", { key: 'fe6984143db817a7b3020a3f57cf5418fc3dcc0e', class: "native-wrapper" }, this.renderToggleControl()))));
36745
+ }, part: "label", id: inputLabelId, onClick: this.onDivLabelClick }, hAsync("slot", { key: '7b162b7dd27199cca2a4c995276a18b9f8e44aaf' }), this.renderHintText()), hAsync("div", { key: 'd13c34bd42fca01cc73ddb4ea7e471b33a282a3e', class: "native-wrapper" }, this.renderToggleControl()))));
36696
36746
  }
36697
36747
  get el() { return getElement(this); }
36698
36748
  static get watchers() { return {
@@ -36718,7 +36768,9 @@ class Toggle {
36718
36768
  "justify": [1],
36719
36769
  "alignment": [1],
36720
36770
  "required": [4],
36721
- "activated": [32]
36771
+ "activated": [32],
36772
+ "isInvalid": [32],
36773
+ "hintTextId": [32]
36722
36774
  },
36723
36775
  "$listeners$": undefined,
36724
36776
  "$lazyBundleId$": "-",