voyager-ionic-core 8.7.9 → 8.7.11

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 (64) hide show
  1. package/components/checkbox.js +63 -9
  2. package/components/ion-datetime.js +35 -2
  3. package/components/ion-input.js +2 -1
  4. package/components/ion-select.js +7 -6
  5. package/components/ion-textarea.js +2 -1
  6. package/components/ion-toggle.js +62 -12
  7. package/components/notch-controller.js +153 -0
  8. package/components/radio-group.js +60 -7
  9. package/components/validity.js +1 -150
  10. package/dist/cjs/ion-checkbox.cjs.entry.js +60 -8
  11. package/dist/cjs/ion-datetime_3.cjs.entry.js +35 -2
  12. package/dist/cjs/ion-input.cjs.entry.js +3 -2
  13. package/dist/cjs/ion-radio_2.cjs.entry.js +57 -6
  14. package/dist/cjs/ion-select_3.cjs.entry.js +7 -6
  15. package/dist/cjs/ion-textarea.cjs.entry.js +3 -2
  16. package/dist/cjs/ion-toggle.cjs.entry.js +58 -10
  17. package/dist/cjs/ionic.cjs.js +1 -1
  18. package/dist/cjs/loader.cjs.js +1 -1
  19. package/dist/cjs/{validity-C8QoAYT2.js → notch-controller-Bzqhjm4f.js} +0 -14
  20. package/dist/cjs/validity-BpS37YFM.js +19 -0
  21. package/dist/collection/components/checkbox/checkbox.js +67 -9
  22. package/dist/collection/components/datetime/datetime.js +35 -2
  23. package/dist/collection/components/radio-group/radio-group.js +64 -7
  24. package/dist/collection/components/select/select.js +5 -5
  25. package/dist/collection/components/toggle/toggle.js +62 -12
  26. package/dist/collection/utils/test/playwright/page/utils/set-content.js +7 -0
  27. package/dist/docs.json +1 -1
  28. package/dist/esm/ion-checkbox.entry.js +60 -8
  29. package/dist/esm/ion-datetime_3.entry.js +35 -2
  30. package/dist/esm/ion-input.entry.js +2 -1
  31. package/dist/esm/ion-radio_2.entry.js +57 -6
  32. package/dist/esm/ion-select_3.entry.js +6 -5
  33. package/dist/esm/ion-textarea.entry.js +2 -1
  34. package/dist/esm/ion-toggle.entry.js +58 -10
  35. package/dist/esm/ionic.js +1 -1
  36. package/dist/esm/loader.js +1 -1
  37. package/dist/esm/{validity-B8oWougr.js → notch-controller-BwelN_JM.js} +1 -14
  38. package/dist/esm/validity-DJztqcrH.js +17 -0
  39. package/dist/ionic/ionic.esm.js +1 -1
  40. package/dist/ionic/p-40c261a3.entry.js +4 -0
  41. package/dist/ionic/p-4e41ea20.entry.js +4 -0
  42. package/dist/ionic/p-7380261c.entry.js +4 -0
  43. package/dist/ionic/{p-DieJyvMP.js → p-DCv9sLH2.js} +1 -1
  44. package/dist/ionic/p-DJztqcrH.js +4 -0
  45. package/dist/ionic/p-c19f63d0.entry.js +4 -0
  46. package/dist/ionic/p-cb93126d.entry.js +4 -0
  47. package/dist/ionic/p-d1f54e28.entry.js +4 -0
  48. package/dist/ionic/p-d3014190.entry.js +4 -0
  49. package/dist/types/components/checkbox/checkbox.d.ts +9 -1
  50. package/dist/types/components/datetime/datetime.d.ts +10 -0
  51. package/dist/types/components/radio-group/radio-group.d.ts +9 -1
  52. package/dist/types/components/select/select.d.ts +2 -2
  53. package/dist/types/components/toggle/toggle.d.ts +7 -1
  54. package/dist/types/utils/forms/validity.d.ts +1 -1
  55. package/hydrate/index.js +312 -227
  56. package/hydrate/index.mjs +312 -227
  57. package/package.json +2 -2
  58. package/dist/ionic/p-4cc26913.entry.js +0 -4
  59. package/dist/ionic/p-4efea47a.entry.js +0 -4
  60. package/dist/ionic/p-7bcfc421.entry.js +0 -4
  61. package/dist/ionic/p-8bdfc8f6.entry.js +0 -4
  62. package/dist/ionic/p-dc2e126d.entry.js +0 -4
  63. package/dist/ionic/p-f65f9308.entry.js +0 -4
  64. 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,
@@ -13022,6 +13237,28 @@ class Datetime {
13022
13237
  destroyKeyboardMO();
13023
13238
  }
13024
13239
  };
13240
+ /**
13241
+ * TODO(FW-6931): Remove this fallback upon solving the root cause
13242
+ * Fallback to ensure the datetime becomes ready even if
13243
+ * IntersectionObserver never reports it as intersecting.
13244
+ *
13245
+ * This is primarily used in environments where the observer
13246
+ * might not fire as expected, such as when running under
13247
+ * synthetic tests that stub IntersectionObserver.
13248
+ */
13249
+ this.ensureReadyIfVisible = () => {
13250
+ if (this.el.classList.contains('datetime-ready')) {
13251
+ return;
13252
+ }
13253
+ const rect = this.el.getBoundingClientRect();
13254
+ if (rect.width === 0 || rect.height === 0) {
13255
+ return;
13256
+ }
13257
+ this.initializeListeners();
13258
+ writeTask(() => {
13259
+ this.el.classList.add('datetime-ready');
13260
+ });
13261
+ };
13025
13262
  this.processValue = (value) => {
13026
13263
  const hasValue = value !== null && value !== undefined && value !== '' && (!Array.isArray(value) || value.length > 0);
13027
13264
  const valueToProcess = hasValue ? parseDate(value) : this.defaultParts;
@@ -13339,6 +13576,17 @@ class Datetime {
13339
13576
  * triggering the `hiddenIO` observer below.
13340
13577
  */
13341
13578
  raf(() => visibleIO === null || visibleIO === void 0 ? void 0 : visibleIO.observe(intersectionTrackerRef));
13579
+ /**
13580
+ * TODO(FW-6931): Remove this fallback upon solving the root cause
13581
+ * Fallback: If IntersectionObserver never reports that the
13582
+ * datetime is visible but the host clearly has layout, ensure
13583
+ * we still initialize listeners and mark the component as ready.
13584
+ *
13585
+ * We schedule this after everything has had a chance to run.
13586
+ */
13587
+ setTimeout(() => {
13588
+ this.ensureReadyIfVisible();
13589
+ }, 100);
13342
13590
  /**
13343
13591
  * We need to clean up listeners when the datetime is hidden
13344
13592
  * in a popover/modal so that we can properly scroll containers
@@ -14094,7 +14342,7 @@ class Datetime {
14094
14342
  const hasDatePresentation = presentation === 'date' || presentation === 'date-time' || presentation === 'time-date';
14095
14343
  const hasWheelVariant = hasDatePresentation && preferWheel;
14096
14344
  renderHiddenInput(true, el, name, formatValue(value), disabled);
14097
- return (hAsync(Host, { key: '57492534800ea059a7c2bbd9f0059cc0b75ae8d2', "aria-disabled": disabled ? 'true' : null, onFocus: this.onFocus, onBlur: this.onBlur, class: Object.assign({}, createColorClasses$1(color, {
14345
+ return (hAsync(Host, { key: 'efdbc0922670a841bc667ceac392cdc1dedffd01', "aria-disabled": disabled ? 'true' : null, onFocus: this.onFocus, onBlur: this.onBlur, class: Object.assign({}, createColorClasses$1(color, {
14098
14346
  [mode]: true,
14099
14347
  ['datetime-readonly']: readonly,
14100
14348
  ['datetime-disabled']: disabled,
@@ -14104,7 +14352,7 @@ class Datetime {
14104
14352
  [`datetime-size-${size}`]: true,
14105
14353
  [`datetime-prefer-wheel`]: hasWheelVariant,
14106
14354
  [`datetime-grid`]: isGridStyle,
14107
- })) }, hAsync("div", { key: '97dac5e5195635ac0bc5fb472b9d09e5c3c6bbc3', class: "intersection-tracker", ref: (el) => (this.intersectionTrackerRef = el) }), this.renderDatetime(mode)));
14355
+ })) }, hAsync("div", { key: '3f8bb75fcb0baff55182ef3aa1b535eacc58d81f', class: "intersection-tracker", ref: (el) => (this.intersectionTrackerRef = el) }), this.renderDatetime(mode)));
14108
14356
  }
14109
14357
  get el() { return getElement(this); }
14110
14358
  static get watchers() { return {
@@ -16312,202 +16560,6 @@ class InfiniteScrollContent {
16312
16560
  }; }
16313
16561
  }
16314
16562
 
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
16563
  /**
16512
16564
  * Used to update a scoped component that uses emulated slots. This fires when
16513
16565
  * content is passed into the slot or when the content inside of a slot changes.
@@ -28051,6 +28103,10 @@ class RadioGroup {
28051
28103
  this.helperTextId = `${this.inputId}-helper-text`;
28052
28104
  this.errorTextId = `${this.inputId}-error-text`;
28053
28105
  this.labelId = `${this.inputId}-lbl`;
28106
+ /**
28107
+ * Track validation state for proper aria-live announcements.
28108
+ */
28109
+ this.isInvalid = false;
28054
28110
  /**
28055
28111
  * If `true`, the radios can be deselected.
28056
28112
  */
@@ -28132,6 +28188,18 @@ class RadioGroup {
28132
28188
  this.labelId = label.id = this.name + '-lbl';
28133
28189
  }
28134
28190
  }
28191
+ // Always set initial state
28192
+ this.isInvalid = checkInvalidState(this.el);
28193
+ }
28194
+ componentWillLoad() {
28195
+ this.hintTextId = this.getHintTextId();
28196
+ }
28197
+ disconnectedCallback() {
28198
+ // Clean up validation observer to prevent memory leaks.
28199
+ if (this.validationObserver) {
28200
+ this.validationObserver.disconnect();
28201
+ this.validationObserver = undefined;
28202
+ }
28135
28203
  }
28136
28204
  getRadios() {
28137
28205
  return Array.from(this.el.querySelectorAll('ion-radio'));
@@ -28207,16 +28275,16 @@ class RadioGroup {
28207
28275
  * Renders the helper text or error text values
28208
28276
  */
28209
28277
  renderHintText() {
28210
- const { helperText, errorText, helperTextId, errorTextId } = this;
28278
+ const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
28211
28279
  const hasHintText = !!helperText || !!errorText;
28212
28280
  if (!hasHintText) {
28213
28281
  return;
28214
28282
  }
28215
- return (hAsync("div", { class: "radio-group-top" }, hAsync("div", { id: helperTextId, class: "helper-text" }, helperText), hAsync("div", { id: errorTextId, class: "error-text" }, errorText)));
28283
+ 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
28284
  }
28217
- getHintTextID() {
28218
- const { el, helperText, errorText, helperTextId, errorTextId } = this;
28219
- if (el.classList.contains('ion-touched') && el.classList.contains('ion-invalid') && errorText) {
28285
+ getHintTextId() {
28286
+ const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
28287
+ if (isInvalid && errorText) {
28220
28288
  return errorTextId;
28221
28289
  }
28222
28290
  if (helperText) {
@@ -28228,7 +28296,7 @@ class RadioGroup {
28228
28296
  const { label, labelId, el, name, value } = this;
28229
28297
  const mode = getIonMode$1(this);
28230
28298
  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' }))));
28299
+ 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
28300
  }
28233
28301
  get el() { return getElement(this); }
28234
28302
  static get watchers() { return {
@@ -28248,6 +28316,8 @@ class RadioGroup {
28248
28316
  "value": [1032],
28249
28317
  "helperText": [1, "helper-text"],
28250
28318
  "errorText": [1, "error-text"],
28319
+ "isInvalid": [32],
28320
+ "hintTextId": [32],
28251
28321
  "setFocus": [64]
28252
28322
  },
28253
28323
  "$listeners$": [[4, "keydown", "onKeydown"]],
@@ -33376,7 +33446,7 @@ class Select {
33376
33446
  }
33377
33447
  componentWillLoad() {
33378
33448
  this.inheritedAttributes = inheritAttributes$1(this.el, ['aria-label']);
33379
- this.hintTextID = this.getHintTextID();
33449
+ this.hintTextId = this.getHintTextId();
33380
33450
  }
33381
33451
  componentDidLoad() {
33382
33452
  /**
@@ -33875,9 +33945,9 @@ class Select {
33875
33945
  }
33876
33946
  renderListbox() {
33877
33947
  const { disabled, inputId, isExpanded, required } = this;
33878
- 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) }));
33948
+ 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) }));
33879
33949
  }
33880
- getHintTextID() {
33950
+ getHintTextId() {
33881
33951
  const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
33882
33952
  if (isInvalid && errorText) {
33883
33953
  return errorTextId;
@@ -34002,7 +34072,7 @@ class Select {
34002
34072
  "isExpanded": [32],
34003
34073
  "hasFocus": [32],
34004
34074
  "isInvalid": [32],
34005
- "hintTextID": [32],
34075
+ "hintTextId": [32],
34006
34076
  "open": [64]
34007
34077
  },
34008
34078
  "$listeners$": undefined,
@@ -36473,6 +36543,10 @@ class Toggle {
36473
36543
  this.inheritedAttributes = {};
36474
36544
  this.didLoad = false;
36475
36545
  this.activated = false;
36546
+ /**
36547
+ * Track validation state for proper aria-live announcements.
36548
+ */
36549
+ this.isInvalid = false;
36476
36550
  /**
36477
36551
  * The name of the control, which is submitted with the form data.
36478
36552
  */
@@ -36586,15 +36660,18 @@ class Toggle {
36586
36660
  });
36587
36661
  }
36588
36662
  async connectedCallback() {
36663
+ const { didLoad, el } = this;
36589
36664
  /**
36590
36665
  * If we have not yet rendered
36591
36666
  * ion-toggle, then toggleTrack is not defined.
36592
36667
  * But if we are moving ion-toggle via appendChild,
36593
36668
  * then toggleTrack will be defined.
36594
36669
  */
36595
- if (this.didLoad) {
36670
+ if (didLoad) {
36596
36671
  this.setupGesture();
36597
36672
  }
36673
+ // Always set initial state
36674
+ this.isInvalid = checkInvalidState(el);
36598
36675
  }
36599
36676
  componentDidLoad() {
36600
36677
  this.setupGesture();
@@ -36605,9 +36682,15 @@ class Toggle {
36605
36682
  this.gesture.destroy();
36606
36683
  this.gesture = undefined;
36607
36684
  }
36685
+ // Clean up validation observer to prevent memory leaks.
36686
+ if (this.validationObserver) {
36687
+ this.validationObserver.disconnect();
36688
+ this.validationObserver = undefined;
36689
+ }
36608
36690
  }
36609
36691
  componentWillLoad() {
36610
36692
  this.inheritedAttributes = Object.assign({}, inheritAriaAttributes(this.el));
36693
+ this.hintTextId = this.getHintTextId();
36611
36694
  }
36612
36695
  onStart() {
36613
36696
  this.activated = true;
@@ -36648,9 +36731,9 @@ class Toggle {
36648
36731
  get hasLabel() {
36649
36732
  return this.el.textContent !== '';
36650
36733
  }
36651
- getHintTextID() {
36652
- const { el, helperText, errorText, helperTextId, errorTextId } = this;
36653
- if (el.classList.contains('ion-touched') && el.classList.contains('ion-invalid') && errorText) {
36734
+ getHintTextId() {
36735
+ const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
36736
+ if (isInvalid && errorText) {
36654
36737
  return errorTextId;
36655
36738
  }
36656
36739
  if (helperText) {
@@ -36663,7 +36746,7 @@ class Toggle {
36663
36746
  * This element should only be rendered if hint text is set.
36664
36747
  */
36665
36748
  renderHintText() {
36666
- const { helperText, errorText, helperTextId, errorTextId } = this;
36749
+ const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
36667
36750
  /**
36668
36751
  * undefined and empty string values should
36669
36752
  * be treated as not having helper/error text.
@@ -36672,15 +36755,15 @@ class Toggle {
36672
36755
  if (!hasHintText) {
36673
36756
  return;
36674
36757
  }
36675
- 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)));
36758
+ 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)));
36676
36759
  }
36677
36760
  render() {
36678
- const { activated, alignment, checked, color, disabled, el, errorTextId, hasLabel, inheritedAttributes, inputId, inputLabelId, justify, labelPlacement, name, required, } = this;
36761
+ const { activated, alignment, checked, color, disabled, el, hasLabel, inheritedAttributes, inputId, inputLabelId, justify, labelPlacement, name, required, } = this;
36679
36762
  const mode = getIonMode$1(this);
36680
36763
  const value = this.getValue();
36681
36764
  const rtl = isRTL$1(el) ? 'rtl' : 'ltr';
36682
36765
  renderHiddenInput(true, el, name, checked ? value : '', disabled);
36683
- 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, {
36766
+ 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, {
36684
36767
  [mode]: true,
36685
36768
  'in-item': hostContext('ion-item', el),
36686
36769
  'toggle-activated': activated,
@@ -36690,10 +36773,10 @@ class Toggle {
36690
36773
  [`toggle-alignment-${alignment}`]: alignment !== undefined,
36691
36774
  [`toggle-label-placement-${labelPlacement}`]: true,
36692
36775
  [`toggle-${rtl}`]: true,
36693
- }) }, 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: {
36776
+ }) }, 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: {
36694
36777
  'label-text-wrapper': true,
36695
36778
  'label-text-wrapper-hidden': !hasLabel,
36696
- }, part: "label", id: inputLabelId, onClick: this.onDivLabelClick }, hAsync("slot", { key: '8322b9d54dc7edeb4e16fefcde9f7ebca8d5c3e1' }), this.renderHintText()), hAsync("div", { key: 'fe6984143db817a7b3020a3f57cf5418fc3dcc0e', class: "native-wrapper" }, this.renderToggleControl()))));
36779
+ }, part: "label", id: inputLabelId, onClick: this.onDivLabelClick }, hAsync("slot", { key: '7b162b7dd27199cca2a4c995276a18b9f8e44aaf' }), this.renderHintText()), hAsync("div", { key: 'd13c34bd42fca01cc73ddb4ea7e471b33a282a3e', class: "native-wrapper" }, this.renderToggleControl()))));
36697
36780
  }
36698
36781
  get el() { return getElement(this); }
36699
36782
  static get watchers() { return {
@@ -36719,7 +36802,9 @@ class Toggle {
36719
36802
  "justify": [1],
36720
36803
  "alignment": [1],
36721
36804
  "required": [4],
36722
- "activated": [32]
36805
+ "activated": [32],
36806
+ "isInvalid": [32],
36807
+ "hintTextId": [32]
36723
36808
  },
36724
36809
  "$listeners$": undefined,
36725
36810
  "$lazyBundleId$": "-",