@aurodesignsystem/auro-formkit 5.10.0 → 5.11.0

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 (77) hide show
  1. package/CHANGELOG.md +55 -15
  2. package/components/bibtemplate/dist/auro-bibtemplate.d.ts +6 -0
  3. package/components/bibtemplate/dist/index.js +12 -0
  4. package/components/bibtemplate/dist/registered.js +12 -0
  5. package/components/checkbox/demo/api.min.js +3 -3
  6. package/components/checkbox/demo/index.min.js +3 -3
  7. package/components/checkbox/dist/index.js +3 -3
  8. package/components/checkbox/dist/registered.js +3 -3
  9. package/components/combobox/demo/api.min.js +1140 -305
  10. package/components/combobox/demo/index.min.js +1140 -305
  11. package/components/combobox/dist/auro-combobox.d.ts +24 -1
  12. package/components/combobox/dist/comboboxKeyboardStrategy.d.ts +6 -0
  13. package/components/combobox/dist/index.js +1082 -264
  14. package/components/combobox/dist/registered.js +1082 -264
  15. package/components/counter/demo/api.min.js +583 -172
  16. package/components/counter/demo/index.min.js +583 -172
  17. package/components/counter/dist/auro-counter.d.ts +8 -0
  18. package/components/counter/dist/buttonVersion.d.ts +1 -1
  19. package/components/counter/dist/iconVersion.d.ts +1 -1
  20. package/components/counter/dist/index.js +583 -172
  21. package/components/counter/dist/registered.js +583 -172
  22. package/components/datepicker/demo/api.md +108 -85
  23. package/components/datepicker/demo/api.min.js +872 -257
  24. package/components/datepicker/demo/index.md +18 -12
  25. package/components/datepicker/demo/index.min.js +872 -257
  26. package/components/datepicker/dist/auro-calendar.d.ts +6 -0
  27. package/components/datepicker/dist/auro-datepicker.d.ts +11 -1
  28. package/components/datepicker/dist/index.js +819 -208
  29. package/components/datepicker/dist/registered.js +819 -208
  30. package/components/dropdown/demo/api.md +15 -17
  31. package/components/dropdown/demo/api.min.js +537 -183
  32. package/components/dropdown/demo/index.min.js +537 -183
  33. package/components/dropdown/dist/auro-dropdown.d.ts +27 -1
  34. package/components/dropdown/dist/auro-dropdownBib.d.ts +87 -0
  35. package/components/dropdown/dist/index.js +514 -160
  36. package/components/dropdown/dist/keyboardUtils.d.ts +18 -0
  37. package/components/dropdown/dist/registered.js +514 -160
  38. package/components/form/README.md +47 -2
  39. package/components/form/demo/api.js +2 -0
  40. package/components/form/demo/api.md +303 -30
  41. package/components/form/demo/api.min.js +69256 -62
  42. package/components/form/demo/index.html +0 -1
  43. package/components/form/demo/index.js +1 -0
  44. package/components/form/demo/index.md +1 -275
  45. package/components/form/demo/index.min.js +69255 -62
  46. package/components/form/demo/readme.md +47 -2
  47. package/components/form/demo/working.html +123 -32
  48. package/components/form/dist/auro-form.d.ts +98 -61
  49. package/components/form/dist/index.js +135 -51
  50. package/components/form/dist/registered.js +135 -51
  51. package/components/input/demo/api.md +1 -0
  52. package/components/input/demo/api.min.js +78 -24
  53. package/components/input/demo/index.min.js +78 -24
  54. package/components/input/dist/base-input.d.ts +34 -0
  55. package/components/input/dist/index.js +78 -24
  56. package/components/input/dist/registered.js +78 -24
  57. package/components/menu/demo/api.md +4 -10
  58. package/components/menu/demo/api.min.js +18 -5
  59. package/components/menu/demo/index.min.js +18 -5
  60. package/components/menu/dist/auro-menuoption.d.ts +0 -8
  61. package/components/menu/dist/iconVersion.d.ts +1 -1
  62. package/components/menu/dist/index.js +18 -5
  63. package/components/menu/dist/registered.js +18 -5
  64. package/components/radio/demo/api.min.js +3 -3
  65. package/components/radio/demo/index.min.js +3 -3
  66. package/components/radio/dist/index.js +3 -3
  67. package/components/radio/dist/registered.js +3 -3
  68. package/components/select/demo/api.js +2 -0
  69. package/components/select/demo/api.md +333 -78
  70. package/components/select/demo/api.min.js +945 -282
  71. package/components/select/demo/index.min.js +933 -282
  72. package/components/select/dist/auro-select.d.ts +26 -0
  73. package/components/select/dist/index.js +881 -247
  74. package/components/select/dist/registered.js +881 -247
  75. package/components/select/dist/selectKeyboardStrategy.d.ts +8 -0
  76. package/custom-elements.json +596 -89
  77. package/package.json +7 -5
@@ -1,6 +1,8 @@
1
1
  import { css, LitElement, html as html$1 } from 'lit';
2
2
  import { unsafeStatic, literal, html } from 'lit/static-html.js';
3
3
  import { classMap } from 'lit/directives/class-map.js';
4
+ import 'lit-html';
5
+ import 'lit-html/directives/unsafe-html.js';
4
6
  import { createRef, ref } from 'lit/directives/ref.js';
5
7
  import { ifDefined } from 'lit/directives/if-defined.js';
6
8
  import { repeat } from 'lit/directives/repeat.js';
@@ -884,7 +886,7 @@ let AuroFormValidation$1 = class AuroFormValidation {
884
886
  }
885
887
  }
886
888
 
887
- if (!hasValue && elem.required && elem.touched) {
889
+ if (!hasValue && elem.required && (force || elem.touched)) {
888
890
  elem.validity = 'valueMissing';
889
891
  elem.errorMessage = elem.setCustomValidityValueMissing || elem.setCustomValidity || '';
890
892
  } else if (hasValue && this.runtimeUtils.elementMatch(elem, 'auro-input')) {
@@ -908,7 +910,7 @@ let AuroFormValidation$1 = class AuroFormValidation {
908
910
  if (!isCombobox || isCombobox && !elem.persistInput) {
909
911
 
910
912
  // run validation on all inputs since we're going to use them to set the validity of this component
911
- this.auroInputElements.forEach(input => input.validate());
913
+ this.auroInputElements.forEach(input => input.validate(force));
912
914
 
913
915
  // Reset element validity to the validity of the input
914
916
  elem.validity = this.auroInputElements[0].validity;
@@ -999,6 +1001,233 @@ let AuroFormValidation$1 = class AuroFormValidation {
999
1001
  }
1000
1002
  };
1001
1003
 
1004
+ /**
1005
+ * Announces text to screen readers via an `aria-live` region inside the given shadow root.
1006
+ *
1007
+ * Expects the shadow root to contain an element with `id="srAnnouncement"`.
1008
+ * The text is cleared and re-set inside a `requestAnimationFrame` so that
1009
+ * repeated identical announcements still fire, and is cleared again after
1010
+ * {@link ANNOUNCEMENT_DURATION_MS} so VoiceOver cannot swipe to stale text.
1011
+ *
1012
+ * @param {ShadowRoot} shadowRoot - The shadow root containing the live region.
1013
+ * @param {string} text - The text to announce.
1014
+ */
1015
+
1016
+ const ANNOUNCEMENT_DURATION_MS = 1000;
1017
+
1018
+ function announceToScreenReader(shadowRoot, text) {
1019
+ const liveRegion = shadowRoot.querySelector('#srAnnouncement');
1020
+ if (liveRegion) {
1021
+ // Clear and re-set to ensure the announcement fires even with same text
1022
+ liveRegion.textContent = '';
1023
+ requestAnimationFrame(() => {
1024
+ liveRegion.textContent = text;
1025
+
1026
+ // Clear after the announcement so VoiceOver cannot swipe to stale text
1027
+ setTimeout(() => {
1028
+ liveRegion.textContent = '';
1029
+ }, ANNOUNCEMENT_DURATION_MS);
1030
+ });
1031
+ }
1032
+ }
1033
+
1034
+ /**
1035
+ * Schedules a callback after two animation frames.
1036
+ *
1037
+ * Used when opening a fullscreen dialog to wait for the dialog to render
1038
+ * (first frame) and then for a Lit update cycle to complete (second frame)
1039
+ * before performing an action like focusing the close button.
1040
+ *
1041
+ * @param {Function} fn - The callback to execute after two animation frames.
1042
+ */
1043
+ function doubleRaf(fn) {
1044
+ requestAnimationFrame(() => {
1045
+ requestAnimationFrame(fn);
1046
+ });
1047
+ }
1048
+
1049
+ /**
1050
+ * Prevents touch pass-through when a fullscreen dialog opens on a touch device.
1051
+ *
1052
+ * On coarse-pointer devices (phones / tablets), the tap that opens the
1053
+ * fullscreen dialog can "pass through" to content beneath the finger —
1054
+ * the touchstart opens the dialog, but the finger is still on the screen,
1055
+ * so the subsequent touchend / click lands on whatever element sits at
1056
+ * those coordinates (e.g. a menu option or calendar cell), selecting it
1057
+ * unintentionally. This does NOT happen with mouse clicks because
1058
+ * showModal() promotes the dialog to the top layer synchronously and the
1059
+ * click has already completed.
1060
+ *
1061
+ * Guard: only activates on devices whose primary input is coarse.
1062
+ * Laptops with a touchscreen report `pointer: fine` (trackpad / mouse is
1063
+ * primary) so they are unaffected. Re-enables on the next touchstart,
1064
+ * which is the user's first deliberate gesture inside the dialog.
1065
+ *
1066
+ * @param {HTMLElement} element - The element to disable pointer events on
1067
+ * (e.g. the menu or calendar wrapper).
1068
+ */
1069
+ function guardTouchPassthrough(element) {
1070
+ if (!element || !window.matchMedia('(pointer: coarse)').matches) return;
1071
+
1072
+ element.style.pointerEvents = 'none';
1073
+ document.addEventListener('touchstart', () => {
1074
+ element.style.pointerEvents = '';
1075
+ }, { once: true });
1076
+ }
1077
+
1078
+ /**
1079
+ * Restores the dropdown trigger after a fullscreen dialog closes.
1080
+ *
1081
+ * Removes the `inert` attribute from the trigger so it is accessible again,
1082
+ * and restores focus to the given target after one animation frame. The rAF
1083
+ * delay lets Lit's microtask update cycle call `dialog.close()` first —
1084
+ * without it the browser's native dialog focus restoration can conflict.
1085
+ *
1086
+ * The focus is only applied if the dropdown is still closed at the time the
1087
+ * rAF fires, guarding against a rapid close-then-reopen race.
1088
+ *
1089
+ * @param {HTMLElement} dropdown - The `auro-dropdown` element.
1090
+ * @param {HTMLElement} focusTarget - The element to focus (e.g. trigger or input).
1091
+ */
1092
+ function restoreTriggerAfterClose(dropdown, focusTarget) {
1093
+ dropdown.trigger.inert = false;
1094
+
1095
+ if (dropdown.isBibFullscreen) {
1096
+ requestAnimationFrame(() => {
1097
+ if (!dropdown.isPopoverVisible) {
1098
+ focusTarget.focus();
1099
+ }
1100
+ });
1101
+ }
1102
+ }
1103
+
1104
+ /**
1105
+ * Wires up a keydown listener that dispatches to strategy[evt.key] or strategy.default.
1106
+ * Handles both sync and async handlers.
1107
+ * @param {HTMLElement} component - The component to attach the listener to.
1108
+ * @param {Object} strategy - Map of key names to handler functions.
1109
+ */
1110
+ function applyKeyboardStrategy(component, strategy) {
1111
+ component.addEventListener('keydown', async (evt) => {
1112
+ const handler = strategy[evt.key] || strategy.default;
1113
+ if (handler) {
1114
+ await handler(component, evt);
1115
+ }
1116
+ });
1117
+ }
1118
+
1119
+ /**
1120
+ * Shared arrow navigation. Calls menu.navigateOptions(direction) if visible.
1121
+ * Optionally opens dropdown via showFn when closed.
1122
+ * @param {HTMLElement} component - The component with dropdown and menu references.
1123
+ * @param {string} direction - 'up' or 'down'.
1124
+ * @param {Object} [options] - Optional config.
1125
+ * @param {Function} [options.showFn] - Called to open the dropdown when closed.
1126
+ */
1127
+ function navigateArrow(component, direction, options = {}) {
1128
+ if (component.dropdown.isPopoverVisible) {
1129
+ component.menu.navigateOptions(direction);
1130
+ } else if (options.showFn) {
1131
+ options.showFn();
1132
+ }
1133
+ }
1134
+
1135
+ const comboboxKeyboardStrategy = {
1136
+ async Enter(component, evt) {
1137
+ // If the clear button has focus, let the browser activate it normally.
1138
+ const clearBtn = component.input.shadowRoot.querySelector('.clearBtn');
1139
+ if (clearBtn && clearBtn.shadowRoot && clearBtn.shadowRoot.activeElement !== null) {
1140
+ return;
1141
+ }
1142
+
1143
+ if (component.dropdown.isPopoverVisible && component.optionActive) {
1144
+ component.menu.makeSelection();
1145
+ await component.updateComplete;
1146
+ evt.preventDefault();
1147
+ evt.stopPropagation();
1148
+ component.setClearBtnFocus();
1149
+ } else {
1150
+ component.showBib();
1151
+ }
1152
+ },
1153
+
1154
+ Tab(component) {
1155
+ if (!component.dropdown.isPopoverVisible) {
1156
+ return;
1157
+ }
1158
+
1159
+ if (component.dropdown.isBibFullscreen) {
1160
+ const clearBtn = component.inputInBib.shadowRoot.querySelector('.clearBtn');
1161
+
1162
+ // Use shadowRoot.activeElement to detect focus inside auro-button,
1163
+ // since Safari does not propagate :focus-within through shadow DOM.
1164
+ const clearBtnHasFocus = clearBtn && clearBtn.shadowRoot && clearBtn.shadowRoot.activeElement !== null;
1165
+
1166
+ // Tab from input: if clear button exists and doesn't have focus, focus it
1167
+ if (clearBtn && !clearBtnHasFocus && component.inputInBib.value) {
1168
+ // Force clear button container visible to work around Safari not
1169
+ // propagating :focus-within through shadow DOM boundaries, which
1170
+ // causes .wrapper:not(:focus-within) to hide .notification.clear.
1171
+ const clearContainer = clearBtn.closest('.clear');
1172
+ if (clearContainer) {
1173
+ clearContainer.style.display = 'flex';
1174
+ clearBtn.addEventListener('focusout', () => {
1175
+ // Delay cleanup so :focus-within settles when focus moves
1176
+ // to a sibling (e.g., Shift+Tab back to the input).
1177
+ requestAnimationFrame(() => {
1178
+ clearContainer.style.display = '';
1179
+ });
1180
+ }, { once: true });
1181
+ }
1182
+
1183
+ // Focus the native button inside auro-button so the browser
1184
+ // treats it as a real focusable element inside the dialog.
1185
+ const nativeBtn = clearBtn.shadowRoot && clearBtn.shadowRoot.querySelector('button');
1186
+ if (nativeBtn) {
1187
+ nativeBtn.focus();
1188
+ } else {
1189
+ clearBtn.focus();
1190
+ }
1191
+ return;
1192
+ }
1193
+
1194
+ // Tab from clear button (or no clear button / no value) →
1195
+ // select the highlighted option if any, then close
1196
+ if (component.optionActive) {
1197
+ component.menu.makeSelection();
1198
+ }
1199
+ component.hideBib();
1200
+ return;
1201
+ }
1202
+
1203
+ // Non-fullscreen: select + close
1204
+ if (component.menu.optionActive && component.menu.optionActive.value) {
1205
+ component.menu.value = component.menu.optionActive.value;
1206
+ }
1207
+ component.hideBib();
1208
+ },
1209
+
1210
+ ArrowUp(component, evt) {
1211
+ if (component.availableOptions.length > 0) {
1212
+ component.showBib();
1213
+ }
1214
+ if (component.dropdown.isPopoverVisible) {
1215
+ evt.preventDefault();
1216
+ navigateArrow(component, 'up');
1217
+ }
1218
+ },
1219
+
1220
+ ArrowDown(component, evt) {
1221
+ if (component.availableOptions.length > 0) {
1222
+ component.showBib();
1223
+ }
1224
+ if (component.dropdown.isPopoverVisible) {
1225
+ evt.preventDefault();
1226
+ navigateArrow(component, 'down');
1227
+ }
1228
+ },
1229
+ };
1230
+
1002
1231
  // Copyright (c) Alaska Air. All right reserved. Licensed under the Apache-2.0 license
1003
1232
  // See LICENSE in the project root for license information.
1004
1233
 
@@ -2726,11 +2955,9 @@ const computePosition = (reference, floating, options) => {
2726
2955
  /* eslint-disable line-comment-position, no-inline-comments */
2727
2956
 
2728
2957
 
2729
-
2730
2958
  const MAX_CONFIGURATION_COUNT = 10;
2731
2959
 
2732
2960
  class AuroFloatingUI {
2733
-
2734
2961
  /**
2735
2962
  * @private
2736
2963
  */
@@ -2745,7 +2972,11 @@ class AuroFloatingUI {
2745
2972
  * @private
2746
2973
  */
2747
2974
  static setupMousePressChecker() {
2748
- if (!AuroFloatingUI.isMousePressHandlerInitialized && window && window.addEventListener) {
2975
+ if (
2976
+ !AuroFloatingUI.isMousePressHandlerInitialized &&
2977
+ window &&
2978
+ window.addEventListener
2979
+ ) {
2749
2980
  AuroFloatingUI.isMousePressHandlerInitialized = true;
2750
2981
 
2751
2982
  // Track timeout for isMousePressed reset to avoid race conditions
@@ -2753,7 +2984,7 @@ class AuroFloatingUI {
2753
2984
  AuroFloatingUI._mousePressedTimeout = null;
2754
2985
  }
2755
2986
  const mouseEventGlobalHandler = (event) => {
2756
- const isPressed = event.type === 'mousedown';
2987
+ const isPressed = event.type === "mousedown";
2757
2988
  if (isPressed) {
2758
2989
  // Clear any pending timeout to prevent race condition
2759
2990
  if (AuroFloatingUI._mousePressedTimeout !== null) {
@@ -2772,8 +3003,8 @@ class AuroFloatingUI {
2772
3003
  }
2773
3004
  };
2774
3005
 
2775
- window.addEventListener('mousedown', mouseEventGlobalHandler);
2776
- window.addEventListener('mouseup', mouseEventGlobalHandler);
3006
+ window.addEventListener("mousedown", mouseEventGlobalHandler);
3007
+ window.addEventListener("mouseup", mouseEventGlobalHandler);
2777
3008
  }
2778
3009
  }
2779
3010
 
@@ -2821,11 +3052,12 @@ class AuroFloatingUI {
2821
3052
  // mirror the boxsize from bibSizer
2822
3053
  if (this.element.bibSizer && this.element.matchWidth) {
2823
3054
  const sizerStyle = window.getComputedStyle(this.element.bibSizer);
2824
- const bibContent = this.element.bib.shadowRoot.querySelector(".container");
2825
- if (sizerStyle.width !== '0px') {
3055
+ const bibContent =
3056
+ this.element.bib.shadowRoot.querySelector(".container");
3057
+ if (sizerStyle.width !== "0px") {
2826
3058
  bibContent.style.width = sizerStyle.width;
2827
3059
  }
2828
- if (sizerStyle.height !== '0px') {
3060
+ if (sizerStyle.height !== "0px") {
2829
3061
  bibContent.style.height = sizerStyle.height;
2830
3062
  }
2831
3063
  bibContent.style.maxWidth = sizerStyle.maxWidth;
@@ -2843,28 +3075,34 @@ class AuroFloatingUI {
2843
3075
  * @returns {String} The positioning strategy, one of 'fullscreen', 'floating', 'cover'.
2844
3076
  */
2845
3077
  getPositioningStrategy() {
2846
- const breakpoint = this.element.bib.mobileFullscreenBreakpoint || this.element.floaterConfig?.fullscreenBreakpoint;
3078
+ const breakpoint =
3079
+ this.element.bib.mobileFullscreenBreakpoint ||
3080
+ this.element.floaterConfig?.fullscreenBreakpoint;
2847
3081
  switch (this.behavior) {
2848
3082
  case "tooltip":
2849
3083
  return "floating";
2850
3084
  case "dialog":
2851
3085
  case "drawer":
2852
3086
  if (breakpoint) {
2853
- const smallerThanBreakpoint = window.matchMedia(`(max-width: ${breakpoint})`).matches;
3087
+ const smallerThanBreakpoint = window.matchMedia(
3088
+ `(max-width: ${breakpoint})`,
3089
+ ).matches;
2854
3090
 
2855
3091
  this.element.expanded = smallerThanBreakpoint;
2856
3092
  }
2857
3093
  if (this.element.nested) {
2858
3094
  return "cover";
2859
3095
  }
2860
- return 'fullscreen';
3096
+ return "fullscreen";
2861
3097
  case "dropdown":
2862
3098
  case undefined:
2863
3099
  case null:
2864
3100
  if (breakpoint) {
2865
- const smallerThanBreakpoint = window.matchMedia(`(max-width: ${breakpoint})`).matches;
3101
+ const smallerThanBreakpoint = window.matchMedia(
3102
+ `(max-width: ${breakpoint})`,
3103
+ ).matches;
2866
3104
  if (smallerThanBreakpoint) {
2867
- return 'fullscreen';
3105
+ return "fullscreen";
2868
3106
  }
2869
3107
  }
2870
3108
  return "floating";
@@ -2885,37 +3123,39 @@ class AuroFloatingUI {
2885
3123
  const strategy = this.getPositioningStrategy();
2886
3124
  this.configureBibStrategy(strategy);
2887
3125
 
2888
- if (strategy === 'floating') {
3126
+ if (strategy === "floating") {
2889
3127
  this.mirrorSize();
2890
3128
  // Define the middlware for the floater configuration
2891
3129
  const middleware = [
2892
3130
  offset(this.element.floaterConfig?.offset || 0),
2893
- ...this.element.floaterConfig?.shift ? [shift()] : [], // Add shift middleware if shift is enabled.
2894
- ...this.element.floaterConfig?.flip ? [flip()] : [], // Add flip middleware if flip is enabled.
2895
- ...this.element.floaterConfig?.autoPlacement ? [autoPlacement()] : [], // Add autoPlacement middleware if autoPlacement is enabled.
3131
+ ...(this.element.floaterConfig?.shift ? [shift()] : []), // Add shift middleware if shift is enabled.
3132
+ ...(this.element.floaterConfig?.flip ? [flip()] : []), // Add flip middleware if flip is enabled.
3133
+ ...(this.element.floaterConfig?.autoPlacement ? [autoPlacement()] : []), // Add autoPlacement middleware if autoPlacement is enabled.
2896
3134
  ];
2897
3135
 
2898
3136
  // Compute the position of the bib
2899
3137
  computePosition(this.element.trigger, this.element.bib, {
2900
- strategy: this.element.floaterConfig?.strategy || 'fixed',
3138
+ strategy: this.element.floaterConfig?.strategy || "fixed",
2901
3139
  placement: this.element.floaterConfig?.placement,
2902
- middleware: middleware || []
2903
- }).then(({ x, y }) => { // eslint-disable-line id-length
3140
+ middleware: middleware || [],
3141
+ }).then(({ x, y }) => {
3142
+ // eslint-disable-line id-length
2904
3143
  Object.assign(this.element.bib.style, {
2905
3144
  left: `${x}px`,
2906
3145
  top: `${y}px`,
2907
3146
  });
2908
3147
  });
2909
- } else if (strategy === 'cover') {
3148
+ } else if (strategy === "cover") {
2910
3149
  // Compute the position of the bib
2911
3150
  computePosition(this.element.parentNode, this.element.bib, {
2912
- placement: 'bottom-start'
2913
- }).then(({ x, y }) => { // eslint-disable-line id-length
3151
+ placement: "bottom-start",
3152
+ }).then(({ x, y }) => {
3153
+ // eslint-disable-line id-length
2914
3154
  Object.assign(this.element.bib.style, {
2915
3155
  left: `${x}px`,
2916
3156
  top: `${y - this.element.parentNode.offsetHeight}px`,
2917
3157
  width: `${this.element.parentNode.offsetWidth}px`,
2918
- height: `${this.element.parentNode.offsetHeight}px`
3158
+ height: `${this.element.parentNode.offsetHeight}px`,
2919
3159
  });
2920
3160
  });
2921
3161
  }
@@ -2928,12 +3168,12 @@ class AuroFloatingUI {
2928
3168
  */
2929
3169
  lockScroll(lock = true) {
2930
3170
  if (lock) {
2931
- document.body.style.overflow = 'hidden'; // hide body's scrollbar
3171
+ document.body.style.overflow = "hidden"; // hide body's scrollbar
2932
3172
 
2933
3173
  // Move `bib` by the amount the viewport is shifted to stay aligned in fullscreen.
2934
3174
  this.element.bib.style.transform = `translateY(${window?.visualViewport?.offsetTop}px)`;
2935
3175
  } else {
2936
- document.body.style.overflow = '';
3176
+ document.body.style.overflow = "";
2937
3177
  }
2938
3178
  }
2939
3179
 
@@ -2947,23 +3187,24 @@ class AuroFloatingUI {
2947
3187
  * @param {string} strategy - The positioning strategy ('fullscreen' or 'floating').
2948
3188
  */
2949
3189
  configureBibStrategy(value) {
2950
- if (value === 'fullscreen') {
3190
+ if (value === "fullscreen") {
2951
3191
  this.element.isBibFullscreen = true;
2952
3192
  // reset the prev position
2953
- this.element.bib.setAttribute('isfullscreen', "");
2954
- this.element.bib.style.position = 'fixed';
3193
+ this.element.bib.setAttribute("isfullscreen", "");
3194
+ this.element.bib.style.position = "fixed";
2955
3195
  this.element.bib.style.top = "0px";
2956
3196
  this.element.bib.style.left = "0px";
2957
- this.element.bib.style.width = '';
2958
- this.element.bib.style.height = '';
2959
- this.element.style.contain = '';
3197
+ this.element.bib.style.width = "";
3198
+ this.element.bib.style.height = "";
3199
+ this.element.style.contain = "";
2960
3200
 
2961
3201
  // reset the size that was mirroring `size` css-part
2962
- const bibContent = this.element.bib.shadowRoot.querySelector(".container");
3202
+ const bibContent =
3203
+ this.element.bib.shadowRoot.querySelector(".container");
2963
3204
  if (bibContent) {
2964
- bibContent.style.width = '';
2965
- bibContent.style.height = '';
2966
- bibContent.style.maxWidth = '';
3205
+ bibContent.style.width = "";
3206
+ bibContent.style.height = "";
3207
+ bibContent.style.maxWidth = "";
2967
3208
  bibContent.style.maxHeight = `${window?.visualViewport?.height}px`;
2968
3209
  this.configureTrial = 0;
2969
3210
  } else if (this.configureTrial < MAX_CONFIGURATION_COUNT) {
@@ -2978,21 +3219,26 @@ class AuroFloatingUI {
2978
3219
  this.lockScroll(true);
2979
3220
  }
2980
3221
  } else {
2981
- this.element.bib.style.position = '';
2982
- this.element.bib.removeAttribute('isfullscreen');
3222
+ this.element.bib.style.position = "";
3223
+ this.element.bib.removeAttribute("isfullscreen");
2983
3224
  this.element.isBibFullscreen = false;
2984
- this.element.style.contain = 'layout';
3225
+ this.element.style.contain = "layout";
2985
3226
  }
2986
3227
 
2987
3228
  const isChanged = this.strategy && this.strategy !== value;
2988
3229
  this.strategy = value;
2989
3230
  if (isChanged) {
2990
- const event = new CustomEvent(this.eventPrefix ? `${this.eventPrefix}-strategy-change` : 'strategy-change', {
2991
- detail: {
2992
- value,
3231
+ const event = new CustomEvent(
3232
+ this.eventPrefix
3233
+ ? `${this.eventPrefix}-strategy-change`
3234
+ : "strategy-change",
3235
+ {
3236
+ detail: {
3237
+ value,
3238
+ },
3239
+ composed: true,
2993
3240
  },
2994
- composed: true
2995
- });
3241
+ );
2996
3242
 
2997
3243
  this.element.dispatchEvent(event);
2998
3244
  }
@@ -3024,19 +3270,24 @@ class AuroFloatingUI {
3024
3270
  return;
3025
3271
  }
3026
3272
 
3027
- if (this.element.noHideOnThisFocusLoss ||
3028
- this.element.hasAttribute('noHideOnThisFocusLoss')) {
3273
+ if (
3274
+ this.element.noHideOnThisFocusLoss ||
3275
+ this.element.hasAttribute("noHideOnThisFocusLoss")
3276
+ ) {
3029
3277
  return;
3030
3278
  }
3031
3279
 
3032
3280
  const { activeElement } = document;
3033
3281
  // if focus is still inside of trigger or bib, do not close
3034
- if (this.element.contains(activeElement) || this.element.bib?.contains(activeElement)) {
3282
+ if (
3283
+ this.element.contains(activeElement) ||
3284
+ this.element.bib?.contains(activeElement)
3285
+ ) {
3035
3286
  return;
3036
3287
  }
3037
3288
 
3038
3289
  // if fullscreen bib is in fullscreen mode, do not close
3039
- if (this.element.bib.hasAttribute('isfullscreen')) {
3290
+ if (this.element.bib.hasAttribute("isfullscreen")) {
3040
3291
  return;
3041
3292
  }
3042
3293
 
@@ -3048,12 +3299,27 @@ class AuroFloatingUI {
3048
3299
  this.focusHandler = () => this.handleFocusLoss();
3049
3300
 
3050
3301
  this.clickHandler = (evt) => {
3051
- if ((!evt.composedPath().includes(this.element.trigger) &&
3052
- !evt.composedPath().includes(this.element.bib)) ||
3053
- (this.element.bib.backdrop && evt.composedPath().includes(this.element.bib.backdrop))) {
3054
- const existedVisibleFloatingUI = document.expandedAuroFormkitDropdown || document.expandedAuroFloater;
3302
+ // When the bib is fullscreen (modal dialog), don't close on outside
3303
+ // clicks. VoiceOver's synthetic click events inside a top-layer modal
3304
+ // <dialog> may not include the bib in composedPath(), causing false
3305
+ // positives. This mirrors the fullscreen guard in handleFocusLoss().
3306
+ if (this.element.bib && this.element.bib.hasAttribute("isfullscreen")) {
3307
+ return;
3308
+ }
3055
3309
 
3056
- if (existedVisibleFloatingUI && existedVisibleFloatingUI.element.isPopoverVisible) {
3310
+ if (
3311
+ (!evt.composedPath().includes(this.element.trigger) &&
3312
+ !evt.composedPath().includes(this.element.bib)) ||
3313
+ (this.element.bib.backdrop &&
3314
+ evt.composedPath().includes(this.element.bib.backdrop))
3315
+ ) {
3316
+ const existedVisibleFloatingUI =
3317
+ document.expandedAuroFormkitDropdown || document.expandedAuroFloater;
3318
+
3319
+ if (
3320
+ existedVisibleFloatingUI &&
3321
+ existedVisibleFloatingUI.element.isPopoverVisible
3322
+ ) {
3057
3323
  // if something else is open, close that
3058
3324
  existedVisibleFloatingUI.hideBib();
3059
3325
  document.expandedAuroFormkitDropdown = null;
@@ -3066,9 +3332,14 @@ class AuroFloatingUI {
3066
3332
 
3067
3333
  // ESC key handler
3068
3334
  this.keyDownHandler = (evt) => {
3069
- if (evt.key === 'Escape' && this.element.isPopoverVisible) {
3070
- const existedVisibleFloatingUI = document.expandedAuroFormkitDropdown || document.expandedAuroFloater;
3071
- if (existedVisibleFloatingUI && existedVisibleFloatingUI !== this && existedVisibleFloatingUI.element.isPopoverVisible) {
3335
+ if (evt.key === "Escape" && this.element.isPopoverVisible) {
3336
+ const existedVisibleFloatingUI =
3337
+ document.expandedAuroFormkitDropdown || document.expandedAuroFloater;
3338
+ if (
3339
+ existedVisibleFloatingUI &&
3340
+ existedVisibleFloatingUI !== this &&
3341
+ existedVisibleFloatingUI.element.isPopoverVisible
3342
+ ) {
3072
3343
  // if something else is open, let it handle itself
3073
3344
  return;
3074
3345
  }
@@ -3076,17 +3347,17 @@ class AuroFloatingUI {
3076
3347
  }
3077
3348
  };
3078
3349
 
3079
- if (this.behavior !== 'drawer' && this.behavior !== 'dialog') {
3350
+ if (this.behavior !== "drawer" && this.behavior !== "dialog") {
3080
3351
  // Add event listeners using the stored references
3081
- document.addEventListener('focusin', this.focusHandler);
3352
+ document.addEventListener("focusin", this.focusHandler);
3082
3353
  }
3083
3354
 
3084
- document.addEventListener('keydown', this.keyDownHandler);
3355
+ document.addEventListener("keydown", this.keyDownHandler);
3085
3356
 
3086
3357
  // send this task to the end of queue to prevent conflicting
3087
3358
  // it conflicts if showBib gets call from a button that's not this.element.trigger
3088
3359
  setTimeout(() => {
3089
- window.addEventListener('click', this.clickHandler);
3360
+ window.addEventListener("click", this.clickHandler);
3090
3361
  }, 0);
3091
3362
  }
3092
3363
 
@@ -3094,34 +3365,38 @@ class AuroFloatingUI {
3094
3365
  // Remove event listeners if they exist
3095
3366
 
3096
3367
  if (this.focusHandler) {
3097
- document.removeEventListener('focusin', this.focusHandler);
3368
+ document.removeEventListener("focusin", this.focusHandler);
3098
3369
  this.focusHandler = null;
3099
3370
  }
3100
3371
 
3101
3372
  if (this.clickHandler) {
3102
- window.removeEventListener('click', this.clickHandler);
3373
+ window.removeEventListener("click", this.clickHandler);
3103
3374
  this.clickHandler = null;
3104
3375
  }
3105
3376
 
3106
3377
  if (this.keyDownHandler) {
3107
- document.removeEventListener('keydown', this.keyDownHandler);
3378
+ document.removeEventListener("keydown", this.keyDownHandler);
3108
3379
  this.keyDownHandler = null;
3109
3380
  }
3110
3381
  }
3111
3382
 
3112
3383
  handleUpdate(changedProperties) {
3113
- if (changedProperties.has('isPopoverVisible')) {
3384
+ if (changedProperties.has("isPopoverVisible")) {
3114
3385
  this.updateState();
3115
3386
  }
3116
3387
  }
3117
3388
 
3118
3389
  updateCurrentExpandedDropdown() {
3119
3390
  // Close any other dropdown that is already open
3120
- const existedVisibleFloatingUI = document.expandedAuroFormkitDropdown || document.expandedAuroFloater;
3121
- if (existedVisibleFloatingUI && existedVisibleFloatingUI !== this &&
3391
+ const existedVisibleFloatingUI =
3392
+ document.expandedAuroFormkitDropdown || document.expandedAuroFloater;
3393
+ if (
3394
+ existedVisibleFloatingUI &&
3395
+ existedVisibleFloatingUI !== this &&
3122
3396
  existedVisibleFloatingUI.element.isPopoverVisible &&
3123
- document.expandedAuroFloater.eventPrefix === this.eventPrefix) {
3124
- document.expandedAuroFloater.hideBib();
3397
+ existedVisibleFloatingUI.eventPrefix === this.eventPrefix
3398
+ ) {
3399
+ existedVisibleFloatingUI.hideBib();
3125
3400
  }
3126
3401
 
3127
3402
  document.expandedAuroFloater = this;
@@ -3130,7 +3405,7 @@ class AuroFloatingUI {
3130
3405
  showBib() {
3131
3406
  if (!this.element.disabled && !this.showing) {
3132
3407
  this.updateCurrentExpandedDropdown();
3133
- this.element.triggerChevron?.setAttribute('data-expanded', true);
3408
+ this.element.triggerChevron?.setAttribute("data-expanded", true);
3134
3409
 
3135
3410
  // prevent double showing: isPopovervisible gets first and showBib gets called later
3136
3411
  if (!this.showing) {
@@ -3144,9 +3419,13 @@ class AuroFloatingUI {
3144
3419
  }
3145
3420
 
3146
3421
  // Setup auto update to handle resize and scroll
3147
- this.element.cleanup = autoUpdate(this.element.trigger || this.element.parentNode, this.element.bib, () => {
3148
- this.position();
3149
- });
3422
+ this.element.cleanup = autoUpdate(
3423
+ this.element.trigger || this.element.parentNode,
3424
+ this.element.bib,
3425
+ () => {
3426
+ this.position();
3427
+ },
3428
+ );
3150
3429
  }
3151
3430
  }
3152
3431
 
@@ -3157,7 +3436,7 @@ class AuroFloatingUI {
3157
3436
  hideBib(eventType = "unknown") {
3158
3437
  if (!this.element.disabled && !this.element.noToggle) {
3159
3438
  this.lockScroll(false);
3160
- this.element.triggerChevron?.removeAttribute('data-expanded');
3439
+ this.element.triggerChevron?.removeAttribute("data-expanded");
3161
3440
 
3162
3441
  if (this.element.isPopoverVisible) {
3163
3442
  this.element.isPopoverVisible = false;
@@ -3177,13 +3456,16 @@ class AuroFloatingUI {
3177
3456
  * @param {String} eventType - The event type that triggered the toggle action.
3178
3457
  */
3179
3458
  dispatchEventDropdownToggle(eventType) {
3180
- const event = new CustomEvent(this.eventPrefix ? `${this.eventPrefix}-toggled` : 'toggled', {
3181
- detail: {
3182
- expanded: this.showing,
3183
- eventType: eventType || "unknown",
3459
+ const event = new CustomEvent(
3460
+ this.eventPrefix ? `${this.eventPrefix}-toggled` : "toggled",
3461
+ {
3462
+ detail: {
3463
+ expanded: this.showing,
3464
+ eventType: eventType || "unknown",
3465
+ },
3466
+ composed: true,
3184
3467
  },
3185
- composed: true
3186
- });
3468
+ );
3187
3469
 
3188
3470
  this.element.dispatchEvent(event);
3189
3471
  }
@@ -3195,12 +3477,15 @@ class AuroFloatingUI {
3195
3477
  this.showBib();
3196
3478
  }
3197
3479
 
3198
- const event = new CustomEvent(this.eventPrefix ? `${this.eventPrefix}-triggerClick` : "triggerClick", {
3199
- composed: true,
3200
- detail: {
3201
- expanded: this.element.isPopoverVisible
3202
- }
3203
- });
3480
+ const event = new CustomEvent(
3481
+ this.eventPrefix ? `${this.eventPrefix}-triggerClick` : "triggerClick",
3482
+ {
3483
+ composed: true,
3484
+ detail: {
3485
+ expanded: this.element.isPopoverVisible,
3486
+ },
3487
+ },
3488
+ );
3204
3489
 
3205
3490
  this.element.dispatchEvent(event);
3206
3491
  }
@@ -3208,30 +3493,32 @@ class AuroFloatingUI {
3208
3493
  handleEvent(event) {
3209
3494
  if (!this.element.disableEventShow) {
3210
3495
  switch (event.type) {
3211
- case 'keydown':
3496
+ case "keydown": {
3212
3497
  // Support both Enter and Space keys for accessibility
3213
3498
  // Space is included as it's expected behavior for interactive elements
3214
3499
 
3215
3500
  const origin = event.composedPath()[0];
3216
- if (event.key === 'Enter' || event.key === ' ' && (!origin || origin.tagName !== "INPUT")) {
3217
-
3501
+ if (
3502
+ event.key === "Enter" ||
3503
+ (event.key === " " && (!origin || origin.tagName !== "INPUT"))
3504
+ ) {
3218
3505
  event.preventDefault();
3219
3506
  this.handleClick();
3220
3507
  }
3221
3508
  break;
3222
- case 'mouseenter':
3509
+ }
3510
+ case "mouseenter":
3223
3511
  if (this.element.hoverToggle) {
3224
3512
  this.showBib();
3225
3513
  }
3226
3514
  break;
3227
- case 'mouseleave':
3515
+ case "mouseleave":
3228
3516
  if (this.element.hoverToggle) {
3229
3517
  this.hideBib("mouseleave");
3230
3518
  }
3231
3519
  break;
3232
- case 'focus':
3520
+ case "focus":
3233
3521
  if (this.element.focusShow) {
3234
-
3235
3522
  /*
3236
3523
  This needs to better handle clicking that gives focus -
3237
3524
  currently it shows and then immediately hides the bib
@@ -3239,12 +3526,12 @@ class AuroFloatingUI {
3239
3526
  this.showBib();
3240
3527
  }
3241
3528
  break;
3242
- case 'blur':
3529
+ case "blur":
3243
3530
  // send this task 100ms later queue to
3244
3531
  // wait a frame in case focus moves within the floating element/bib
3245
3532
  setTimeout(() => this.handleFocusLoss(), 0);
3246
3533
  break;
3247
- case 'click':
3534
+ case "click":
3248
3535
  if (document.activeElement === document.body) {
3249
3536
  event.currentTarget.focus();
3250
3537
  }
@@ -3263,15 +3550,15 @@ class AuroFloatingUI {
3263
3550
  */
3264
3551
  handleTriggerTabIndex() {
3265
3552
  const focusableElementSelectors = [
3266
- 'a',
3267
- 'button',
3553
+ "a",
3554
+ "button",
3268
3555
  'input:not([type="hidden"])',
3269
- 'select',
3270
- 'textarea',
3556
+ "select",
3557
+ "textarea",
3271
3558
  '[tabindex]:not([tabindex="-1"])',
3272
- 'auro-button',
3273
- 'auro-input',
3274
- 'auro-hyperlink'
3559
+ "auro-button",
3560
+ "auro-input",
3561
+ "auro-hyperlink",
3275
3562
  ];
3276
3563
 
3277
3564
  const triggerNode = this.element.querySelectorAll('[slot="trigger"]')[0];
@@ -3299,10 +3586,10 @@ class AuroFloatingUI {
3299
3586
  * @param {*} eventPrefix
3300
3587
  */
3301
3588
  regenerateBibId() {
3302
- this.id = this.element.getAttribute('id');
3589
+ this.id = this.element.getAttribute("id");
3303
3590
  if (!this.id) {
3304
3591
  this.id = window.crypto.randomUUID();
3305
- this.element.setAttribute('id', this.id);
3592
+ this.element.setAttribute("id", this.id);
3306
3593
  }
3307
3594
 
3308
3595
  this.element.bib.setAttribute("id", `${this.id}-floater-bib`);
@@ -3323,11 +3610,15 @@ class AuroFloatingUI {
3323
3610
  if (this.element.trigger) {
3324
3611
  this.disconnect();
3325
3612
  }
3326
- this.element.trigger = this.element.triggerElement || this.element.shadowRoot.querySelector('#trigger') || this.element.trigger;
3327
- this.element.bib = this.element.shadowRoot.querySelector('#bib') || this.element.bib;
3328
- this.element.bibSizer = this.element.shadowRoot.querySelector('#bibSizer');
3329
- this.element.triggerChevron = this.element.shadowRoot.querySelector('#showStateIcon');
3330
-
3613
+ this.element.trigger =
3614
+ this.element.triggerElement ||
3615
+ this.element.shadowRoot.querySelector("#trigger") ||
3616
+ this.element.trigger;
3617
+ this.element.bib =
3618
+ this.element.shadowRoot.querySelector("#bib") || this.element.bib;
3619
+ this.element.bibSizer = this.element.shadowRoot.querySelector("#bibSizer");
3620
+ this.element.triggerChevron =
3621
+ this.element.shadowRoot.querySelector("#showStateIcon");
3331
3622
 
3332
3623
  if (this.element.floaterConfig) {
3333
3624
  this.element.hoverToggle = this.element.floaterConfig.hoverToggle;
@@ -3338,12 +3629,12 @@ class AuroFloatingUI {
3338
3629
 
3339
3630
  this.handleEvent = this.handleEvent.bind(this);
3340
3631
  if (this.element.trigger) {
3341
- this.element.trigger.addEventListener('keydown', this.handleEvent);
3342
- this.element.trigger.addEventListener('click', this.handleEvent);
3343
- this.element.trigger.addEventListener('mouseenter', this.handleEvent);
3344
- this.element.trigger.addEventListener('mouseleave', this.handleEvent);
3345
- this.element.trigger.addEventListener('focus', this.handleEvent);
3346
- this.element.trigger.addEventListener('blur', this.handleEvent);
3632
+ this.element.trigger.addEventListener("keydown", this.handleEvent);
3633
+ this.element.trigger.addEventListener("click", this.handleEvent);
3634
+ this.element.trigger.addEventListener("mouseenter", this.handleEvent);
3635
+ this.element.trigger.addEventListener("mouseleave", this.handleEvent);
3636
+ this.element.trigger.addEventListener("focus", this.handleEvent);
3637
+ this.element.trigger.addEventListener("blur", this.handleEvent);
3347
3638
  }
3348
3639
  }
3349
3640
 
@@ -3358,12 +3649,18 @@ class AuroFloatingUI {
3358
3649
 
3359
3650
  // Remove event & keyboard listeners
3360
3651
  if (this.element?.trigger) {
3361
- this.element.trigger.removeEventListener('keydown', this.handleEvent);
3362
- this.element.trigger.removeEventListener('click', this.handleEvent);
3363
- this.element.trigger.removeEventListener('mouseenter', this.handleEvent);
3364
- this.element.trigger.removeEventListener('mouseleave', this.handleEvent);
3365
- this.element.trigger.removeEventListener('focus', this.handleEvent);
3366
- this.element.trigger.removeEventListener('blur', this.handleEvent);
3652
+ this.element.trigger.removeEventListener("keydown", this.handleEvent);
3653
+ this.element.trigger.removeEventListener("click", this.handleEvent);
3654
+ this.element.trigger.removeEventListener(
3655
+ "mouseenter",
3656
+ this.handleEvent,
3657
+ );
3658
+ this.element.trigger.removeEventListener(
3659
+ "mouseleave",
3660
+ this.handleEvent,
3661
+ );
3662
+ this.element.trigger.removeEventListener("focus", this.handleEvent);
3663
+ this.element.trigger.removeEventListener("blur", this.handleEvent);
3367
3664
  }
3368
3665
  }
3369
3666
  }
@@ -3810,7 +4107,7 @@ let p$3 = class p{registerComponent(t,a){customElements.get(t)||customElements.d
3810
4107
 
3811
4108
  var iconVersion$2 = '9.1.2';
3812
4109
 
3813
- var styleCss$2$1 = css`:host{position:fixed;z-index:var(--depth-tooltip, 400);display:none;isolation:isolate}:host(:not([matchWidth])) .container{min-width:fit-content}:host([isfullscreen]){position:fixed;top:0;left:0}:host([isfullscreen]) .container{width:100dvw;max-width:none;height:100dvh;max-height:none;border-radius:unset;margin-top:0;box-shadow:unset;overscroll-behavior:contain}:host([data-show]){display:flex}:host([common]:not([isfullscreen])) .container,:host([rounded]:not([isfullscreen])) .container{border-radius:var(--ds-border-radius, 0.375rem)}:host([common][isfullscreen]) .container,:host([rounded][isfullscreen]) .container{border-radius:unset;box-shadow:unset}:host(:not([isfullscreen])) .container.shape-box{border-radius:unset}:host(:not([isfullscreen])) .container[class*=shape-pill],:host(:not([isfullscreen])) .container[class*=shape-snowflake]{border-radius:30px}:host(:not([isfullscreen])) .container[class*=shape-rounded]{border-radius:16px}.container{display:inline-block;overflow:auto;box-sizing:border-box;border-radius:var(--ds-border-radius, 0.375rem);margin:var(--ds-size-50, 0.25rem) 0}`;
4110
+ var styleCss$2$1 = css`:host{position:fixed;z-index:var(--depth-tooltip, 400);display:none;isolation:isolate}:host dialog{width:auto;max-width:none;height:auto;max-height:none;padding:0;border:none;margin:0;outline:none;transform:translateZ(0)}:host dialog::backdrop{background:transparent}:host(:not([isfullscreen])) dialog{position:relative;inset:unset}:host(:not([isfullscreen])) .container.shape-box{border-radius:unset}:host(:not([isfullscreen])) .container[class*=shape-pill],:host(:not([isfullscreen])) .container[class*=shape-snowflake]{border-radius:30px}:host(:not([isfullscreen])) .container[class*=shape-rounded]{border-radius:16px}:host(:not([matchWidth])) .container{min-width:fit-content}:host([isfullscreen]){position:fixed;top:0;left:0}:host([isfullscreen]) .container{width:100dvw;max-width:none;height:100dvh;max-height:none;border-radius:unset;margin-top:0;box-shadow:unset;overscroll-behavior:contain}:host([isfullscreen]) .container::backdrop{background:var(--ds-color-background-primary, #fff)}:host([data-show]){display:flex}:host([common]:not([isfullscreen])) .container,:host([rounded]:not([isfullscreen])) .container{border-radius:var(--ds-border-radius, 0.375rem)}:host([common][isfullscreen]) .container,:host([rounded][isfullscreen]) .container{border-radius:unset;box-shadow:unset}.container{display:inline-block;overflow:auto;box-sizing:border-box;border-radius:var(--ds-border-radius, 0.375rem);margin:var(--ds-size-50, 0.25rem) 0}.util_displayHiddenVisually{position:absolute;overflow:hidden;width:1px;height:1px;padding:0;border:0;margin:-1px;clip-path:inset(50%);white-space:nowrap}`;
3814
4111
 
3815
4112
  var colorCss$2$1 = css`.container{background-color:var(--ds-auro-dropdownbib-container-color);box-shadow:var(--ds-auro-dropdownbib-boxshadow-color);color:var(--ds-auro-dropdownbib-text-color)}`;
3816
4113
 
@@ -3818,6 +4115,8 @@ var tokensCss$1$1 = css`:host(:not([ondark])),:host(:not([appearance=inverse])){
3818
4115
 
3819
4116
  // Copyright (c) 2020 Alaska Airlines. All right reserved. Licensed under the Apache-2.0 license
3820
4117
  // See LICENSE in the project root for license information.
4118
+ /* eslint-disable max-lines */
4119
+ // ---------------------------------------------------------------------
3821
4120
 
3822
4121
 
3823
4122
  const DESIGN_TOKEN_BREAKPOINT_PREFIX = '--ds-grid-breakpoint-';
@@ -3912,6 +4211,28 @@ class AuroDropdownBib extends LitElement {
3912
4211
  shape: {
3913
4212
  type: String,
3914
4213
  reflect: true
4214
+ },
4215
+
4216
+ /**
4217
+ * Accessible label for the dialog element, used when displayed as a modal.
4218
+ * Applied via aria-labelledby on a visually hidden element rather than
4219
+ * aria-label because iOS VoiceOver does not reliably read aria-label
4220
+ * on <dialog> elements.
4221
+ * @private
4222
+ */
4223
+ dialogLabel: {
4224
+ type: String
4225
+ },
4226
+
4227
+ /**
4228
+ * Overrides the native role of the dialog element.
4229
+ * For example, set to `"presentation"` on desktop combobox to prevent
4230
+ * VoiceOver from announcing "listbox inside of a dialog".
4231
+ * When `undefined`, the dialog keeps its native role.
4232
+ * @private
4233
+ */
4234
+ dialogRole: {
4235
+ type: String
3915
4236
  }
3916
4237
  };
3917
4238
  }
@@ -3979,7 +4300,10 @@ class AuroDropdownBib extends LitElement {
3979
4300
  firstUpdated(changedProperties) {
3980
4301
  super.firstUpdated(changedProperties);
3981
4302
 
3982
- // Dispatch a custom event when the component is connected
4303
+ const dialog = this.shadowRoot.querySelector('dialog');
4304
+ this._setupCancelHandler(dialog);
4305
+ this._setupKeyboardBridge(dialog);
4306
+
3983
4307
  this.dispatchEvent(new CustomEvent('auro-dropdownbib-connected', {
3984
4308
  bubbles: true,
3985
4309
  composed: true,
@@ -3989,6 +4313,189 @@ class AuroDropdownBib extends LitElement {
3989
4313
  }));
3990
4314
  }
3991
4315
 
4316
+ /**
4317
+ * Forwards the dialog's native `cancel` event (fired on ESC) as
4318
+ * an `auro-bib-cancel` custom event so parent components can close.
4319
+ * @param {HTMLDialogElement} dialog
4320
+ * @private
4321
+ */
4322
+ _setupCancelHandler(dialog) {
4323
+ dialog.addEventListener('cancel', (event) => {
4324
+ event.preventDefault();
4325
+ this.dispatchEvent(new CustomEvent('auro-bib-cancel', {
4326
+ bubbles: true,
4327
+ composed: true
4328
+ }));
4329
+ });
4330
+ }
4331
+
4332
+ /**
4333
+ * showModal() creates a closed focus scope — keyboard events inside
4334
+ * the dialog's shadow DOM do NOT bubble out to the combobox/select
4335
+ * keydown handlers in the parent shadow DOM. This handler bridges
4336
+ * that gap by re-dispatching navigation keys so they cross the
4337
+ * shadow boundary and reach the menu navigation logic in the parent
4338
+ * component.
4339
+ *
4340
+ * The trade-off: intercepting these keys means native keyboard
4341
+ * behaviors that would normally "just work" must be manually
4342
+ * re-implemented here:
4343
+ *
4344
+ * - Enter on buttons: Custom elements (auro-button) don't get the
4345
+ * native Enter→click that <button> provides, so we call .click()
4346
+ * directly when Enter is pressed on a button-like element.
4347
+ *
4348
+ * - Tab: Intercepted and re-dispatched so parent components
4349
+ * (select/combobox) can select the active option and close the
4350
+ * dialog. The <dialog> provides containment and isolation
4351
+ * (inert background, VoiceOver focus trapping, top layer), while
4352
+ * the content inside is a role="listbox" navigated via
4353
+ * aria-activedescendant (options are not focusable). Tab keyboard
4354
+ * behavior follows listbox conventions (select + close) because
4355
+ * the dialog's native Tab trap only cycles between the close
4356
+ * button and browser chrome.
4357
+ *
4358
+ * - Escape: The native <dialog> fires a `cancel` event on ESC
4359
+ * (handled by _setupCancelHandler), so the re-dispatched Escape
4360
+ * is a secondary path for parent components that also listen for
4361
+ * Escape keydown.
4362
+ *
4363
+ * @param {HTMLDialogElement} dialog
4364
+ * @private
4365
+ */
4366
+ _setupKeyboardBridge(dialog) {
4367
+ const navKeys = new Set([
4368
+ 'ArrowUp',
4369
+ 'ArrowDown',
4370
+ 'Enter',
4371
+ 'Escape',
4372
+ 'Tab'
4373
+ ]);
4374
+
4375
+ dialog.addEventListener('keydown', (event) => {
4376
+ if (!navKeys.has(event.key)) {
4377
+ return;
4378
+ }
4379
+
4380
+ // Custom elements (auro-button) don't get the native Enter→click
4381
+ // behavior that <button> has. Find the button in the composed path
4382
+ // and click it directly.
4383
+ if (event.key === 'Enter') {
4384
+ const buttonSelector = 'button, [role="button"], auro-button, [auro-button]';
4385
+ const btn = event.composedPath().find((el) => el.matches && el.matches(buttonSelector));
4386
+ if (btn) {
4387
+ event.preventDefault();
4388
+ event.stopPropagation();
4389
+ btn.click();
4390
+ return;
4391
+ }
4392
+ }
4393
+
4394
+ event.preventDefault();
4395
+ event.stopPropagation();
4396
+ const newEvent = new KeyboardEvent('keydown', {
4397
+ key: event.key,
4398
+ code: event.code,
4399
+ shiftKey: event.shiftKey,
4400
+ altKey: event.altKey,
4401
+ ctrlKey: event.ctrlKey,
4402
+ metaKey: event.metaKey,
4403
+ bubbles: true,
4404
+ composed: true,
4405
+ cancelable: true
4406
+ });
4407
+ this.dispatchEvent(newEvent);
4408
+ });
4409
+ }
4410
+
4411
+ /**
4412
+ * Blocks touch-driven page scroll while a fullscreen modal dialog is open.
4413
+ *
4414
+ * The showModal() function places the dialog in the browser's **top layer**,
4415
+ * which is a separate rendering layer above the normal DOM. On mobile, the
4416
+ * compositor processes visual-viewport panning before top-layer touch
4417
+ * handling. This means the entire viewport — including the top-layer dialog
4418
+ * — can be panned by a touch gesture, causing the page behind the dialog to
4419
+ * scroll into view. To prevent this, we add a touchmove listener that cancels
4420
+ * the event if the touch started outside the dialog or any scrollable child within it.
4421
+ *
4422
+ * @private
4423
+ */
4424
+ _lockTouchScroll() {
4425
+ const dialog = this.shadowRoot.querySelector('dialog');
4426
+
4427
+ this._touchMoveHandler = (event) => {
4428
+ // Walk the composed path (which crosses shadow DOM boundaries) to
4429
+ // check whether the touch started inside a scrollable element that
4430
+ // lives within the dialog. If so, allow the scroll.
4431
+ for (const el of event.composedPath()) {
4432
+ if (el === dialog) {
4433
+ // Reached the dialog boundary without finding a scrollable child.
4434
+ break;
4435
+ }
4436
+ if (el instanceof HTMLElement && el.scrollHeight > el.clientHeight) {
4437
+ const { overflowY } = getComputedStyle(el);
4438
+ if (overflowY === 'auto' || overflowY === 'scroll') {
4439
+ return;
4440
+ }
4441
+ }
4442
+ }
4443
+
4444
+ event.preventDefault();
4445
+ };
4446
+
4447
+ document.addEventListener('touchmove', this._touchMoveHandler, { passive: false });
4448
+ }
4449
+
4450
+ /**
4451
+ * Removes the touchmove listener added by _lockTouchScroll().
4452
+ * @private
4453
+ */
4454
+ _unlockTouchScroll() {
4455
+ if (this._touchMoveHandler) {
4456
+ document.removeEventListener('touchmove', this._touchMoveHandler);
4457
+ this._touchMoveHandler = undefined;
4458
+ }
4459
+ }
4460
+
4461
+ open(modal = true) {
4462
+ const dialog = this.shadowRoot.querySelector('dialog');
4463
+ if (dialog && !dialog.open) {
4464
+ if (modal) {
4465
+ // Prevent showModal() from scrolling the page to bring the dialog
4466
+ // into view. Locking overflow on <html> blocks the viewport scroll
4467
+ // that browsers perform natively; we release it immediately after
4468
+ // so it doesn't interfere with the modal's focus management.
4469
+ const { documentElement } = document;
4470
+ const prevOverflow = documentElement.style.overflow;
4471
+ documentElement.style.overflow = 'hidden';
4472
+
4473
+ dialog.showModal();
4474
+
4475
+ documentElement.style.overflow = prevOverflow;
4476
+
4477
+ this._lockTouchScroll();
4478
+
4479
+ } else {
4480
+ // Use setAttribute instead of dialog.show() to avoid the dialog
4481
+ // focusing steps which steal focus from the trigger and cause
4482
+ // the floater's handleFocusLoss() to immediately hide the bib.
4483
+ dialog.setAttribute('open', '');
4484
+ }
4485
+ }
4486
+ }
4487
+
4488
+ /**
4489
+ * Closes the dialog.
4490
+ */
4491
+ close() {
4492
+ const dialog = this.shadowRoot.querySelector('dialog');
4493
+ if (dialog && dialog.open) {
4494
+ this._unlockTouchScroll();
4495
+ dialog.close();
4496
+ }
4497
+ }
4498
+
3992
4499
  // function that renders the HTML and CSS into the scope of the component
3993
4500
  render() {
3994
4501
  const classes = {
@@ -4000,9 +4507,10 @@ class AuroDropdownBib extends LitElement {
4000
4507
  classes[`shape-${this.shape}`] = true;
4001
4508
 
4002
4509
  return html`
4003
- <div class="${classMap(classes)}" part="bibContainer">
4510
+ <dialog class="${classMap(classes)}" part="bibContainer" role="${ifDefined(this.dialogRole)}" aria-labelledby="${ifDefined(this.dialogLabel ? 'dialogLabel' : undefined)}">
4511
+ ${this.dialogLabel ? html`<span id="dialogLabel" class="util_displayHiddenVisually" aria-hidden="true">${this.dialogLabel}</span>` : ''}
4004
4512
  <slot></slot>
4005
- </div>
4513
+ </dialog>
4006
4514
  `;
4007
4515
  }
4008
4516
  }
@@ -4011,7 +4519,7 @@ var shapeSizeCss$1 = css`.shape-classic-xl,.shape-classic-lg,.shape-classic-md,.
4011
4519
 
4012
4520
  var colorCss$1$1 = css`:host(:not([layout*=classic])){--ds-auro-dropdown-trigger-border-color: transparent}:host(:not([disabled],[onDark])) .wrapper:focus-within,:host(:not([disabled],[onDark])) .wrapper:active,:host(:not([disabled],[appearance=inverse])) .wrapper:focus-within,:host(:not([disabled],[appearance=inverse])) .wrapper:active{--ds-auro-dropdown-trigger-border-color: var(--ds-advanced-color-state-focused, #01426a);--ds-auro-dropdown-trigger-outline-color: var(--ds-advanced-color-state-focused, #01426a)}:host(:not([disabled],[onDark])) .wrapper:hover,:host(:not([disabled],[appearance=inverse])) .wrapper:hover{--ds-auro-dropdown-trigger-background-color: var(--ds-auro-dropdown-trigger-hover-background-color)}:host(:not([ondark])) .wrapper,:host(:not([appearance=inverse])) .wrapper{border-color:var(--ds-auro-dropdown-trigger-border-color);background-color:var(--ds-auro-dropdown-trigger-background-color);color:var(--ds-auro-dropdown-trigger-text-color)}:host(:not([onDark])[disabled]),:host(:not([appearance=inverse])[disabled]){--ds-auro-dropdown-trigger-text-color: var(--ds-basic-color-texticon-disabled, #d0d0d0);--ds-auro-dropdown-label-text-color: var(--ds-basic-color-texticon-disabled, #d0d0d0);--ds-auro-dropdown-trigger-border-color: var(--ds-basic-color-border-subtle, #dddddd)}:host(:not([onDark])[disabled]) #triggerLabel,:host(:not([appearance=inverse])[disabled]) #triggerLabel{cursor:default}:host(:not([ondark])[error]),:host(:not([appearance=inverse])[error]){--ds-auro-dropdown-trigger-border-color: var(--ds-basic-color-status-error, #e31f26)}:host(:not([disabled])[onDark]) .wrapper:focus-within,:host(:not([disabled])[onDark]) .wrapper:active,:host(:not([disabled])[appearance=inverse]) .wrapper:focus-within,:host(:not([disabled])[appearance=inverse]) .wrapper:active{--ds-auro-dropdown-trigger-border-color: var(--ds-advanced-color-state-focused-inverse, #ffffff);--ds-auro-dropdown-trigger-outline-color: var(--ds-advanced-color-state-focused-inverse, #ffffff)}:host(:not([disabled])[onDark]) .wrapper:hover,:host(:not([disabled])[appearance=inverse]) .wrapper:hover{--ds-auro-dropdown-trigger-background-color: var(--ds-auro-dropdown-trigger-hover-background-color)}:host([onDark]) .label,:host([onDark]) .helpText,:host([appearance=inverse]) .label,:host([appearance=inverse]) .helpText{color:var(--ds-auro-dropdown-label-text-color)}:host([onDark]) .wrapper,:host([appearance=inverse]) .wrapper{border-color:var(--ds-auro-dropdown-trigger-border-color);background-color:var(--ds-auro-dropdown-trigger-background-color);color:var(--ds-auro-dropdown-trigger-text-color)}:host([onDark][disabled]),:host([appearance=inverse][disabled]){--ds-auro-dropdown-trigger-text-color: var(--ds-basic-color-texticon-inverse-disabled, #7e8894);--ds-auro-dropdown-label-text-color: var(--ds-basic-color-texticon-inverse-disabled, #7e8894);--ds-auro-dropdown-trigger-container-color: var(--ds-advanced-color-shared-background-inverse-disabled, rgba(255, 255, 255, 0.1))}:host([onDark][disabled]) #triggerLabel,:host([appearance=inverse][disabled]) #triggerLabel{cursor:default}:host([ondark][error]),:host([appearance=inverse][error]){--ds-auro-dropdown-trigger-border-color: var(--ds-advanced-color-state-error-inverse, #f9a4a8)}`;
4013
4521
 
4014
- var styleCss$1$2 = css`:host{position:relative;display:block;text-align:left}[popover=manual]{overflow:visible;padding:0;border:inherit;margin:0;background:inherit;outline:inherit}:host([open]){z-index:var(--depth-tooltip, 400)}.wrapper{display:flex;flex:1;flex-direction:row;align-items:center;justify-content:center;outline:none}.triggerContentWrapper{display:flex;overflow:hidden;width:100%;flex:1;align-items:center;justify-content:center;text-overflow:ellipsis;white-space:nowrap}:host([matchwidth]) #bibSizer{width:100%}`;
4522
+ var styleCss$1$2 = css`:host{position:relative;display:block;text-align:left}:host([open]){z-index:var(--depth-tooltip, 400)}.wrapper{display:flex;flex:1;flex-direction:row;align-items:center;justify-content:center;outline:none}.triggerContentWrapper{display:flex;overflow:hidden;width:100%;flex:1;align-items:center;justify-content:center;text-overflow:ellipsis;white-space:nowrap}:host([matchwidth]) #bibSizer{width:100%}`;
4015
4523
 
4016
4524
  var classicColorCss$1 = css``;
4017
4525
 
@@ -4249,7 +4757,7 @@ let AuroHelpText$2 = class AuroHelpText extends LitElement {
4249
4757
  }
4250
4758
  };
4251
4759
 
4252
- var formkitVersion$2 = '202602140013';
4760
+ var formkitVersion$2 = '202603102257';
4253
4761
 
4254
4762
  let AuroElement$2 = class AuroElement extends LitElement {
4255
4763
  static get properties() {
@@ -4363,7 +4871,7 @@ let AuroElement$2 = class AuroElement extends LitElement {
4363
4871
  * The `auro-dropdown` element provides a way to place content in a bib that can be toggled.
4364
4872
  * @customElement auro-dropdown
4365
4873
  *
4366
- * @slot - Default slot for the popover content.
4874
+ * @slot - Default slot for the dropdown bib content.
4367
4875
  * @slot helpText - Defines the content of the helpText.
4368
4876
  * @slot trigger - Defines the content of the trigger.
4369
4877
  * @csspart trigger - The trigger content container.
@@ -4375,6 +4883,13 @@ let AuroElement$2 = class AuroElement extends LitElement {
4375
4883
  * @event auroDropdown-idAdded - Notifies consumers that the unique ID for the dropdown bib has been generated.
4376
4884
  */
4377
4885
  class AuroDropdown extends AuroElement$2 {
4886
+ static get shadowRootOptions() {
4887
+ return {
4888
+ ...AuroElement$2.shadowRootOptions,
4889
+ delegatesFocus: true,
4890
+ };
4891
+ }
4892
+
4378
4893
  constructor() {
4379
4894
  super();
4380
4895
 
@@ -4440,15 +4955,6 @@ class AuroDropdown extends AuroElement$2 {
4440
4955
  this.shift = false;
4441
4956
  this.autoPlacement = false;
4442
4957
 
4443
- /**
4444
- * @private
4445
- * @property {boolean} delegatesFocus - Whether the shadow root delegates focus.
4446
- */
4447
- this.constructor.shadowRootOptions = {
4448
- ...LitElement.shadowRootOptions,
4449
- delegatesFocus: true,
4450
- };
4451
-
4452
4958
  /**
4453
4959
  * @private
4454
4960
  */
@@ -4522,6 +5028,18 @@ class AuroDropdown extends AuroElement$2 {
4522
5028
  */
4523
5029
  show() {
4524
5030
  this.floater.showBib();
5031
+
5032
+ // Open dialog synchronously so callers remain in the user gesture
5033
+ // chain. This is critical for mobile browsers (iOS Safari) to keep
5034
+ // the virtual keyboard open when transitioning from the trigger
5035
+ // input to an input inside the fullscreen dialog. Without this,
5036
+ // showModal() fires asynchronously via Lit's update cycle, which
5037
+ // falls outside the user activation window and causes iOS to
5038
+ // dismiss the keyboard.
5039
+ if (this.isBibFullscreen && this.bibElement && this.bibElement.value) {
5040
+ const useModal = !this.disableFocusTrap;
5041
+ this.bibElement.value.open(useModal);
5042
+ }
4525
5043
  }
4526
5044
 
4527
5045
  /**
@@ -4529,13 +5047,37 @@ class AuroDropdown extends AuroElement$2 {
4529
5047
  * If not, trigger element will get focus.
4530
5048
  */
4531
5049
  focus() {
4532
- if (this.isPopoverVisible && this.focusTrap) {
4533
- this.focusTrap.focusFirstElement();
5050
+ if (this.isPopoverVisible && this.bibContent) {
5051
+ const focusables = getFocusableElements(this.bibContent);
5052
+ if (focusables.length > 0) {
5053
+ focusables[0].focus();
5054
+ }
4534
5055
  } else {
4535
5056
  this.trigger.focus();
4536
5057
  }
4537
5058
  }
4538
5059
 
5060
+ /**
5061
+ * Sets the active descendant element for accessibility.
5062
+ * Uses ariaActiveDescendantElement to cross shadow DOM boundaries.
5063
+ * This function is used in components that contain `auro-dropdown` to set the active descendant.
5064
+ * @private
5065
+ * @param {HTMLElement|null} element - The element to set as the active descendant, or null to clear.
5066
+ * @returns {void}
5067
+ */
5068
+ setActiveDescendant(element) {
5069
+ if (!this.trigger) {
5070
+ return;
5071
+ }
5072
+
5073
+ if (element) {
5074
+ this.trigger.ariaActiveDescendantElement = element;
5075
+ } else {
5076
+ this.trigger.ariaActiveDescendantElement = null;
5077
+ this.trigger.removeAttribute('aria-activedescendant');
5078
+ }
5079
+ }
5080
+
4539
5081
  // function to define props used within the scope of this component
4540
5082
  static get properties() {
4541
5083
  return {
@@ -4793,6 +5335,16 @@ class AuroDropdown extends AuroElement$2 {
4793
5335
  */
4794
5336
  tabIndex: {
4795
5337
  type: Number
5338
+ },
5339
+
5340
+ /**
5341
+ * Accessible label for the dropdown bib dialog element.
5342
+ * @private
5343
+ */
5344
+ bibDialogLabel: {
5345
+ type: String,
5346
+ attribute: false,
5347
+ reflect: false
4796
5348
  }
4797
5349
  };
4798
5350
  }
@@ -4844,7 +5396,10 @@ class AuroDropdown extends AuroElement$2 {
4844
5396
 
4845
5397
  disconnectedCallback() {
4846
5398
  super.disconnectedCallback();
4847
- this.floater.disconnect();
5399
+ if (this.floater) {
5400
+ this.floater.hideBib('disconnect');
5401
+ this.floater.disconnect();
5402
+ }
4848
5403
  this.clearTriggerFocusEventBinding();
4849
5404
  }
4850
5405
 
@@ -4866,11 +5421,22 @@ class AuroDropdown extends AuroElement$2 {
4866
5421
 
4867
5422
  if (changedProperties.has('isPopoverVisible') && this.bibElement.value) {
4868
5423
  if (this.isPopoverVisible) {
4869
- this.bibElement.value.showPopover();
5424
+ // Fullscreen: use showModal() for native accessibility (inert outside, focus trap)
5425
+ // Desktop: use show() for Floating UI positioning + FocusTrap for focus management
5426
+ const useModal = this.isBibFullscreen && !this.disableFocusTrap;
5427
+ this.bibElement.value.open(useModal);
4870
5428
  } else {
4871
- this.bibElement.value.hidePopover();
5429
+ this.bibElement.value.close();
4872
5430
  }
4873
5431
  }
5432
+
5433
+ // When fullscreen strategy changes while open, re-open dialog with correct mode
5434
+ // (e.g. resizing from desktop → mobile while dropdown is open)
5435
+ if (changedProperties.has('isBibFullscreen') && this.isPopoverVisible && this.bibElement.value) {
5436
+ const useModal = this.isBibFullscreen && !this.disableFocusTrap;
5437
+ this.bibElement.value.close();
5438
+ this.bibElement.value.open(useModal);
5439
+ }
4874
5440
  }
4875
5441
 
4876
5442
  /**
@@ -4888,11 +5454,28 @@ class AuroDropdown extends AuroElement$2 {
4888
5454
  }
4889
5455
 
4890
5456
  firstUpdated() {
4891
-
4892
5457
  // Configure the floater to, this will generate the ID for the bib
4893
5458
  this.floater.configure(this, 'auroDropdown');
5459
+
5460
+ // Prevent `contain: layout` on the dropdown host. Layout containment
5461
+ // creates a containing block for position:fixed descendants (the bib),
5462
+ // which clips the bib inside ancestor overflow contexts such as a
5463
+ // <dialog> element. Without it, the bib's position:fixed is relative
5464
+ // to the viewport, letting Floating UI's flip middleware detect
5465
+ // viewport boundaries and the bib escape overflow clipping.
5466
+ const origConfigureBibStrategy = this.floater.configureBibStrategy.bind(this.floater);
5467
+ this.floater.configureBibStrategy = (value) => {
5468
+ origConfigureBibStrategy(value);
5469
+ this.style.contain = '';
5470
+ };
5471
+
4894
5472
  this.addEventListener('auroDropdown-toggled', this.handleDropdownToggle);
4895
5473
 
5474
+ // Handle ESC key from dialog's cancel event
5475
+ this.addEventListener('auro-bib-cancel', () => {
5476
+ this.floater.hideBib('keydown');
5477
+ });
5478
+
4896
5479
  /**
4897
5480
  * @description Let subscribers know that the dropdown ID ha been generated and added.
4898
5481
  * @event auroDropdown-idAdded
@@ -4900,9 +5483,9 @@ class AuroDropdown extends AuroElement$2 {
4900
5483
  */
4901
5484
  this.dispatchEvent(new CustomEvent('auroDropdown-idAdded', {detail: {id: this.floater.element.id}}));
4902
5485
 
4903
- // Set the bib ID locally if the user hasn't provided a focusable trigger
5486
+ // Set the bib ID locally for aria-controls (must be in the same shadow root as the trigger)
4904
5487
  if (!this.triggerContentFocusable) {
4905
- this.dropdownId = this.floater.element.id;
5488
+ this.dropdownId = this.floater.element.bib.id;
4906
5489
  }
4907
5490
 
4908
5491
  this.bibContent = this.floater.element.bib;
@@ -4962,21 +5545,20 @@ class AuroDropdown extends AuroElement$2 {
4962
5545
  * @private
4963
5546
  */
4964
5547
  updateFocusTrap() {
4965
- // If the dropdown is open, create a focus trap and focus the first element
4966
5548
  if (this.isPopoverVisible && !this.disableFocusTrap) {
4967
- this.focusTrap = new FocusTrap(this.bibContent);
4968
- this.focusTrap.focusFirstElement();
5549
+ if (!this.isBibFullscreen) {
5550
+ // Desktop: show() doesn't trap focus, so use FocusTrap
5551
+ this.focusTrap = new FocusTrap(this.bibContent);
5552
+ this.focusTrap.focusFirstElement();
5553
+ }
5554
+ // Fullscreen: showModal() provides native focus trapping
4969
5555
  return;
4970
5556
  }
4971
5557
 
4972
- // Guard Clause: Ensure there is a focus trap currently active before continuing
4973
- if (!this.focusTrap) {
4974
- return;
5558
+ if (this.focusTrap) {
5559
+ this.focusTrap.disconnect();
5560
+ this.focusTrap = undefined;
4975
5561
  }
4976
-
4977
- // If the dropdown is not open, disconnect the focus trap if it exists
4978
- this.focusTrap.disconnect();
4979
- this.focusTrap = undefined;
4980
5562
  }
4981
5563
 
4982
5564
  /**
@@ -5192,13 +5774,14 @@ class AuroDropdown extends AuroElement$2 {
5192
5774
  <div
5193
5775
  id="showStateIcon"
5194
5776
  class="chevron"
5195
- part="chevron">
5777
+ part="chevron"
5778
+ aria-hidden="true">
5196
5779
  <${this.iconTag}
5197
5780
  category="interface"
5198
5781
  name="${this.isPopoverVisible ? 'chevron-up' : `chevron-down`}"
5199
5782
  appearance="${this.onDark ? 'inverse' : this.appearance}"
5200
- variant="${this.disabled ? 'disabled' : 'muted'}">
5201
- >
5783
+ variant="${this.disabled ? 'disabled' : 'muted'}"
5784
+ ariaHidden="true">
5202
5785
  </${this.iconTag}>
5203
5786
  </div>
5204
5787
  ` : undefined }
@@ -5212,8 +5795,8 @@ class AuroDropdown extends AuroElement$2 {
5212
5795
  shape="${this.shape}"
5213
5796
  ?data-show="${this.isPopoverVisible}"
5214
5797
  ?isfullscreen="${this.isBibFullscreen}"
5798
+ .dialogLabel="${this.bibDialogLabel}"
5215
5799
  ${ref(this.bibElement)}
5216
- popover="manual"
5217
5800
  >
5218
5801
  <div class="slotContent">
5219
5802
  <slot @slotchange="${this.handleDefaultSlot}"></slot>
@@ -10127,7 +10710,7 @@ class AuroFormValidation {
10127
10710
  }
10128
10711
  }
10129
10712
 
10130
- if (!hasValue && elem.required && elem.touched) {
10713
+ if (!hasValue && elem.required && (force || elem.touched)) {
10131
10714
  elem.validity = 'valueMissing';
10132
10715
  elem.errorMessage = elem.setCustomValidityValueMissing || elem.setCustomValidity || '';
10133
10716
  } else if (hasValue && this.runtimeUtils.elementMatch(elem, 'auro-input')) {
@@ -10151,7 +10734,7 @@ class AuroFormValidation {
10151
10734
  if (!isCombobox || isCombobox && !elem.persistInput) {
10152
10735
 
10153
10736
  // run validation on all inputs since we're going to use them to set the validity of this component
10154
- this.auroInputElements.forEach(input => input.validate());
10737
+ this.auroInputElements.forEach(input => input.validate(force));
10155
10738
 
10156
10739
  // Reset element validity to the validity of the input
10157
10740
  elem.validity = this.auroInputElements[0].validity;
@@ -10357,6 +10940,17 @@ let AuroElement$1 = class AuroElement extends LitElement {
10357
10940
  */
10358
10941
  class BaseInput extends AuroElement$1 {
10359
10942
 
10943
+ // Delegate focus to the native <input> inside the shadow root so that
10944
+ // showModal()'s dialog focusing steps reach the input element.
10945
+ // This keeps the mobile virtual keyboard open when the fullscreen dialog
10946
+ // opens, because the browser sees an input-to-input focus transfer.
10947
+ static get shadowRootOptions() {
10948
+ return {
10949
+ ...AuroElement$1.shadowRootOptions,
10950
+ delegatesFocus: true,
10951
+ };
10952
+ }
10953
+
10360
10954
  constructor() {
10361
10955
  super();
10362
10956
 
@@ -10374,6 +10968,7 @@ class BaseInput extends AuroElement$1 {
10374
10968
  this.icon = false;
10375
10969
  this.disabled = false;
10376
10970
  this.dvInputOnly = false;
10971
+ this.hideLabelVisually = false;
10377
10972
  this.max = undefined;
10378
10973
  this.maxLength = undefined;
10379
10974
  this.min = undefined;
@@ -10481,6 +11076,15 @@ class BaseInput extends AuroElement$1 {
10481
11076
  reflect: true
10482
11077
  },
10483
11078
 
11079
+ /**
11080
+ * The value for the aria-activedescendant attribute.
11081
+ * Points to the ID of the currently active/highlighted option in a listbox.
11082
+ */
11083
+ a11yActivedescendant: {
11084
+ type: String,
11085
+ reflect: true
11086
+ },
11087
+
10484
11088
  /**
10485
11089
  * If set, the label will remain fixed in the active position.
10486
11090
  */
@@ -10587,6 +11191,16 @@ class BaseInput extends AuroElement$1 {
10587
11191
  attribute: false
10588
11192
  },
10589
11193
 
11194
+ /**
11195
+ * If set, the label will be hidden visually but still accessible to assistive technologies.
11196
+ * @private
11197
+ */
11198
+ hideLabelVisually: {
11199
+ type: Boolean,
11200
+ reflect: true
11201
+ },
11202
+
11203
+
10590
11204
  /**
10591
11205
  * If set, will render an icon inside the input to the left of the value. Support is limited to auro-input instances with credit card format.
10592
11206
  */
@@ -11164,31 +11778,34 @@ class BaseInput extends AuroElement$1 {
11164
11778
  // Process credit card type detection and formatting during input
11165
11779
  if (this.type === 'credit-card') {
11166
11780
  this.processCreditCard();
11167
- }
11781
+ this.touched = true;
11782
+ this.validation.validate(this);
11783
+ } else {
11168
11784
 
11169
- // Sets value property to value of element value (el.value).
11170
- this.value = this.inputElement.value;
11785
+ // Sets value property to value of element value (el.value).
11786
+ this.value = this.inputElement.value;
11171
11787
 
11172
- // Determine if the value change was programmatic, including autofill.
11173
- const inputWasProgrammatic = !this.matches(":focus") || event.isProgrammatic;
11788
+ // Determine if the value change was programmatic, including autofill.
11789
+ const inputWasProgrammatic = !this.matches(":focus") || event.isProgrammatic;
11174
11790
 
11175
- // Validation on input or programmatic value change (including autofill).
11176
- if (this.validateOnInput || inputWasProgrammatic) {
11177
- this.touched = true;
11178
- this.validation.validate(this);
11179
- }
11791
+ // Validation on input or programmatic value change (including autofill).
11792
+ if (this.validateOnInput || inputWasProgrammatic) {
11793
+ this.touched = true;
11794
+ this.validation.validate(this);
11795
+ }
11180
11796
 
11181
- // Prevents cursor jumping in Safari.
11182
- const { selectionStart } = this.inputElement;
11797
+ // Prevents cursor jumping in Safari.
11798
+ const { selectionStart } = this.inputElement;
11183
11799
 
11184
- if (this.setSelectionInputTypes.includes(this.type)) {
11185
- this.updateComplete.then(() => {
11186
- try {
11187
- this.inputElement.setSelectionRange(selectionStart, selectionStart);
11188
- } catch (error) { // eslint-disable-line
11189
- // do nothing
11190
- }
11191
- });
11800
+ if (this.setSelectionInputTypes.includes(this.type)) {
11801
+ this.updateComplete.then(() => {
11802
+ try {
11803
+ this.inputElement.setSelectionRange(selectionStart, selectionStart);
11804
+ } catch (error) { // eslint-disable-line
11805
+ // do nothing
11806
+ }
11807
+ });
11808
+ }
11192
11809
  }
11193
11810
  }
11194
11811
 
@@ -11221,6 +11838,11 @@ class BaseInput extends AuroElement$1 {
11221
11838
  this.inputElement.scrollLeft = 100;
11222
11839
 
11223
11840
  if (!this.noValidate) {
11841
+ // For credit card inputs with mask, ensure value is synced from mask instance
11842
+ if (this.type === 'credit-card' && this.maskInstance) {
11843
+ this.value = this.maskInstance.value;
11844
+ }
11845
+
11224
11846
  this.validation.validate(this);
11225
11847
  }
11226
11848
  }
@@ -11245,6 +11867,20 @@ class BaseInput extends AuroElement$1 {
11245
11867
  return activeEl;
11246
11868
  }
11247
11869
 
11870
+ /**
11871
+ * Sets the active descendant element for accessibility.
11872
+ * Uses ariaActiveDescendantElement to cross shadow DOM boundaries.
11873
+ * This function is used in components that contain `auro-input` to set the active descendant.
11874
+ * @private
11875
+ * @param {HTMLElement|null} element - The element to set as the active descendant, or null to clear.
11876
+ * @returns {void}
11877
+ */
11878
+ setActiveDescendant(element) {
11879
+ if (this.inputElement) {
11880
+ this.inputElement.ariaActiveDescendantElement = element;
11881
+ }
11882
+ }
11883
+
11248
11884
  /**
11249
11885
  * Validates value.
11250
11886
  * @param {boolean} [force=false] - Whether to force validation.
@@ -11869,7 +12505,7 @@ let AuroHelpText$1 = class AuroHelpText extends LitElement {
11869
12505
  }
11870
12506
  };
11871
12507
 
11872
- var formkitVersion$1 = '202602140013';
12508
+ var formkitVersion$1 = '202603102257';
11873
12509
 
11874
12510
  // Copyright (c) 2025 Alaska Airlines. All right reserved. Licensed under the Apache-2.0 license
11875
12511
  // See LICENSE in the project root for license information.
@@ -12051,7 +12687,7 @@ class AuroInput extends BaseInput {
12051
12687
  return {
12052
12688
  'is-disabled': this.disabled,
12053
12689
  'withValue': this.hasValue,
12054
- 'util_displayHiddenVisually': this.labelHidden,
12690
+ 'util_displayHiddenVisually': this.labelHidden || this.hideLabelVisually,
12055
12691
  [this.labelFontClass]: true,
12056
12692
  };
12057
12693
  }
@@ -12223,6 +12859,7 @@ class AuroInput extends BaseInput {
12223
12859
  ?activeLabel="${this.activeLabel}"
12224
12860
  ?disabled="${this.disabled}"
12225
12861
  ?required="${this.required}"
12862
+ aria-activedescendant=${ifDefined(this.a11yActivedescendant)}
12226
12863
  aria-controls=${ifDefined(this.a11yControls)}
12227
12864
  aria-describedby="${this.uniqueId}"
12228
12865
  aria-expanded=${ifDefined(this.a11yExpanded)}
@@ -12837,6 +13474,18 @@ class AuroBibtemplate extends LitElement {
12837
13474
  this.removeEventListener('touchmove', this.preventBodyScroll, { passive: false });
12838
13475
  }
12839
13476
 
13477
+ /**
13478
+ * Focuses the close button inside the bibtemplate's shadow DOM.
13479
+ * Used by parent components to set initial focus when the fullscreen dialog opens.
13480
+ * @returns {void}
13481
+ */
13482
+ focusCloseButton() {
13483
+ const closeBtn = this.shadowRoot.querySelector('#closeButton');
13484
+ if (closeBtn) {
13485
+ closeBtn.focus();
13486
+ }
13487
+ }
13488
+
12840
13489
  onCloseButtonClick() {
12841
13490
  this.dispatchEvent(new Event("close-click", { bubbles: true,
12842
13491
  composed: true }));
@@ -12895,7 +13544,7 @@ class AuroBibtemplate extends LitElement {
12895
13544
  }
12896
13545
  }
12897
13546
 
12898
- var formkitVersion = '202602140013';
13547
+ var formkitVersion = '202603102257';
12899
13548
 
12900
13549
  var styleCss$1 = css`.util_displayInline{display:inline}.util_displayInlineBlock{display:inline-block}.util_displayBlock{display:block}.util_displayFlex{display:flex}.util_displayHidden{display:none}.util_displayHiddenVisually{position:absolute;overflow:hidden;clip:rect(1px, 1px, 1px, 1px);width:1px;height:1px;padding:0;border:0}:host{display:block;text-align:left}:host [auro-dropdown]{--ds-auro-dropdown-trigger-background-color: transparent}:host #inputInBib::part(wrapper){box-shadow:none}:host #inputInBib::part(accent-left){display:none}:host([layout*=classic]) [auro-input]{width:100%}:host([layout*=classic]) [auro-input]::part(helpText){display:none}:host([layout*=classic]) #slotHolder{display:none}`;
12901
13550
 
@@ -13311,6 +13960,9 @@ class AuroCombobox extends AuroElement {
13311
13960
  this.availableOptions = [];
13312
13961
  this.dropdownId = undefined;
13313
13962
  this.dropdownOpen = false;
13963
+ this.triggerExpandedState = false;
13964
+ this._expandedTimeout = null;
13965
+ this._inFullscreenTransition = false;
13314
13966
  this.errorMessage = null;
13315
13967
  this.isHiddenWhileLoading = false;
13316
13968
  this.largeFullscreenHeadline = false;
@@ -13331,7 +13983,7 @@ class AuroCombobox extends AuroElement {
13331
13983
 
13332
13984
  /**
13333
13985
  * Defines whether the component will be on lighter or darker backgrounds.
13334
- * @property {'default' | 'inverse'}
13986
+ * @property {'default' | 'inverse'} appearance - The visual appearance of the component.
13335
13987
  * @default 'default'
13336
13988
  */
13337
13989
  appearance: {
@@ -13667,6 +14319,18 @@ class AuroCombobox extends AuroElement {
13667
14319
  reflect: false,
13668
14320
  attribute: false
13669
14321
  },
14322
+
14323
+ /**
14324
+ * Deferred aria-expanded state for the trigger input.
14325
+ * Delays the "true" transition so VoiceOver finishes its character echo
14326
+ * before announcing "expanded".
14327
+ * @private
14328
+ */
14329
+ triggerExpandedState: {
14330
+ type: Boolean,
14331
+ reflect: false,
14332
+ attribute: false
14333
+ },
13670
14334
  };
13671
14335
  }
13672
14336
 
@@ -13764,6 +14428,8 @@ class AuroCombobox extends AuroElement {
13764
14428
  } else if (!option.hasAttribute('persistent')) {
13765
14429
  // Hide all other non-persistent options
13766
14430
  option.setAttribute('hidden', '');
14431
+ option.removeAttribute('aria-setsize');
14432
+ option.removeAttribute('aria-posinset');
13767
14433
  }
13768
14434
  });
13769
14435
 
@@ -13830,6 +14496,15 @@ class AuroCombobox extends AuroElement {
13830
14496
  this.availableOptions = [];
13831
14497
  this.updateFilter();
13832
14498
 
14499
+ // Set aria-setsize/aria-posinset on each visible option so screen readers
14500
+ // can announce position even when the listbox context is broken by
14501
+ // shadow DOM boundaries in fullscreen dialog mode.
14502
+ const optionCount = this.availableOptions.length;
14503
+ this.availableOptions.forEach((option, index) => {
14504
+ option.setAttribute('aria-setsize', optionCount);
14505
+ option.setAttribute('aria-posinset', index + 1);
14506
+ });
14507
+
13833
14508
  if (this.value && this.input.value && !this.menu.value) {
13834
14509
  this.syncValuesAndStates();
13835
14510
  }
@@ -13863,7 +14538,7 @@ class AuroCombobox extends AuroElement {
13863
14538
  * @returns {void}
13864
14539
  */
13865
14540
  showBib() {
13866
- if (!this.input.value) {
14541
+ if (!this.input.value && !this.dropdown.isBibFullscreen) {
13867
14542
  this.dropdown.hide();
13868
14543
  return;
13869
14544
  }
@@ -13886,6 +14561,10 @@ class AuroCombobox extends AuroElement {
13886
14561
  configureDropdown() {
13887
14562
  this.dropdown.a11yRole = "combobox";
13888
14563
 
14564
+ // Prevent dropdown from closing on focus loss since menu content is slotted
14565
+ // from combobox's light DOM and won't be detected by dropdown's contains() check.
14566
+ this.dropdown.noHideOnThisFocusLoss = true;
14567
+
13889
14568
  // Listen for the ID to be added to the dropdown so we can capture it and use it for accessibility.
13890
14569
  this.dropdown.addEventListener('auroDropdown-idAdded', (event) => {
13891
14570
  this.dropdownId = event.detail.id;
@@ -13896,12 +14575,80 @@ class AuroCombobox extends AuroElement {
13896
14575
  this.dropdownOpen = ev.detail.expanded;
13897
14576
  this.updateMenuShapeSize();
13898
14577
 
13899
- // wait a frame in case the bib gets hide immediately after showing because there is no value
13900
- setTimeout(() => {
13901
- if (this.componentHasFocus) {
13902
- this.setInputFocus();
14578
+ // Defer aria-expanded "true" so VoiceOver finishes character echo
14579
+ // before announcing "expanded". Set "false" immediately on close.
14580
+ clearTimeout(this._expandedTimeout);
14581
+ if (this.dropdownOpen) {
14582
+ const expandedDelay = 150;
14583
+ this._expandedTimeout = setTimeout(() => {
14584
+ this.triggerExpandedState = true;
14585
+ }, expandedDelay);
14586
+ } else {
14587
+ this.triggerExpandedState = false;
14588
+ }
14589
+
14590
+ // Clear aria-activedescendant when dropdown closes
14591
+ if (!this.dropdownOpen && this.input) {
14592
+ this.input.setActiveDescendant(null);
14593
+ this.optionActive = null;
14594
+
14595
+ // Remove the highlighted state from all menu options so re-opening
14596
+ // the dropdown doesn't show a stale highlight.
14597
+ if (this.options) {
14598
+ this.options.forEach((opt) => {
14599
+ opt.active = false;
14600
+ opt.classList.remove('active');
14601
+ });
13903
14602
  }
13904
- }, 0);
14603
+
14604
+ // Restore pointer events on the menu in case they were disabled
14605
+ // during fullscreen open to prevent touch pass-through.
14606
+ this.menu.style.pointerEvents = '';
14607
+
14608
+ restoreTriggerAfterClose(this.dropdown, this.input);
14609
+ }
14610
+
14611
+ if (this.dropdownOpen) {
14612
+ // Suppress or restore dialog semantics based on mode.
14613
+ // On desktop, VoiceOver verbosely announces "listbox inside of a dialog"
14614
+ // which is disruptive for combobox usage. Fullscreen needs dialog semantics.
14615
+ this.updateBibDialogRole();
14616
+
14617
+ if (this.dropdown.isBibFullscreen) {
14618
+ // Guard against spurious validation during the focus transition
14619
+ // from trigger to bib input. Setting trigger.inert = true removes
14620
+ // focus, which fires focusout on the child auro-input before the
14621
+ // bib input receives focus. That focusout triggers the input's own
14622
+ // validate(), which dispatches a composed auroFormElement-validated
14623
+ // event. Because composed events are retargetted at each shadow DOM
14624
+ // boundary, the event appears to originate from the combobox itself
14625
+ // and its listener unconditionally sets this.validity — causing
14626
+ // premature validation. This flag suppresses all validation paths
14627
+ // (focusout handler, handleInputValueChange, validate(), and the
14628
+ // auroFormElement-validated listener) until focus settles in the bib.
14629
+ this._inFullscreenTransition = true;
14630
+
14631
+ // Hide the trigger from assistive technology so VoiceOver cannot reach it
14632
+ // behind the fullscreen dialog
14633
+ this.dropdown.trigger.inert = true;
14634
+
14635
+ guardTouchPassthrough(this.menu);
14636
+
14637
+ // Wait for the bibtemplate to fully render across
14638
+ // multiple Lit update cycles before moving focus into the bib
14639
+ doubleRaf(() => {
14640
+ this.setInputFocus();
14641
+ this._inFullscreenTransition = false;
14642
+ });
14643
+ } else {
14644
+ // wait a frame in case the bib gets hidden immediately after showing because there is no value
14645
+ setTimeout(() => {
14646
+ if (this.componentHasFocus) {
14647
+ this.setInputFocus();
14648
+ }
14649
+ }, 0);
14650
+ }
14651
+ }
13905
14652
  });
13906
14653
 
13907
14654
  this.dropdown.addEventListener('auroDropdown-triggerClick', () => {
@@ -13912,6 +14659,12 @@ class AuroCombobox extends AuroElement {
13912
14659
  this.bibtemplate = this.dropdown.querySelector(this.bibtemplateTag._$litStatic$);
13913
14660
  this.inputInBib = this.bibtemplate.querySelector(this.inputTag._$litStatic$);
13914
14661
 
14662
+ // Pass label text to the dropdown bib for accessible dialog naming
14663
+ const labelElement = this.querySelector('[slot="label"]');
14664
+ if (labelElement) {
14665
+ this.dropdown.bibDialogLabel = labelElement.textContent.trim() || undefined;
14666
+ }
14667
+
13915
14668
  // Exposes the CSS parts from the bibtemplate for styling
13916
14669
  this.bibtemplate.exposeCssParts();
13917
14670
 
@@ -13922,6 +14675,14 @@ class AuroCombobox extends AuroElement {
13922
14675
  this.dropdown.addEventListener('auroDropdown-strategy-change', () => {
13923
14676
  // event when the strategy(bib mode) is changed between fullscreen and floating
13924
14677
  this.updateMenuShapeSize();
14678
+ this.updateBibDialogRole();
14679
+
14680
+ // When switching to fullscreen while open, hide trigger from assistive technology
14681
+ if (this.dropdown.isBibFullscreen && this.dropdown.isPopoverVisible) {
14682
+ this.dropdown.trigger.inert = true;
14683
+ } else if (!this.dropdown.isBibFullscreen) {
14684
+ this.dropdown.trigger.inert = false;
14685
+ }
13925
14686
 
13926
14687
  setTimeout(() => {
13927
14688
  this.setInputFocus();
@@ -13945,6 +14706,13 @@ class AuroCombobox extends AuroElement {
13945
14706
  setInputFocus() {
13946
14707
  if (this.dropdown.isBibFullscreen && this.dropdown.isPopoverVisible) {
13947
14708
  this.inputInBib.focus();
14709
+
14710
+ // Place cursor at end of existing text so the user can continue editing
14711
+ const nativeInput = this.inputInBib.inputElement;
14712
+ if (nativeInput && nativeInput.value) {
14713
+ const len = nativeInput.value.length;
14714
+ nativeInput.setSelectionRange(len, len);
14715
+ }
13948
14716
  } else if (!this.input.componentHasFocus) {
13949
14717
  const focusedEl = this.querySelector(":focus");
13950
14718
  this.input.focus();
@@ -13956,6 +14724,22 @@ class AuroCombobox extends AuroElement {
13956
14724
  }
13957
14725
  }
13958
14726
 
14727
+ /**
14728
+ * Suppresses or restores dialog semantics on the bib's dialog element.
14729
+ * On desktop (non-fullscreen), VoiceOver verbosely announces "listbox inside
14730
+ * of a dialog" which disrupts combobox usage. Setting role="presentation"
14731
+ * suppresses this. In fullscreen mode, dialog semantics are restored.
14732
+ * @private
14733
+ */
14734
+ updateBibDialogRole() {
14735
+ const bibEl = this.dropdown.bibElement && this.dropdown.bibElement.value;
14736
+ if (!bibEl) {
14737
+ return;
14738
+ }
14739
+
14740
+ bibEl.dialogRole = this.dropdown.isBibFullscreen ? undefined : 'presentation';
14741
+ }
14742
+
13959
14743
  /**
13960
14744
  * Update menu to default for fullscreen bib, otherwise to this.size and this.shape.
13961
14745
  * @private
@@ -14041,6 +14825,14 @@ class AuroCombobox extends AuroElement {
14041
14825
  setTimeout(() => {
14042
14826
  this.hideBib();
14043
14827
  }, 0);
14828
+
14829
+ // Announce the selection after the dropdown closes so it isn't
14830
+ // overridden by VoiceOver's "collapsed" announcement from aria-expanded.
14831
+ const selectedValue = event.detail.stringValue;
14832
+ const announcementDelay = 300;
14833
+ setTimeout(() => {
14834
+ announceToScreenReader(this.shadowRoot, `${selectedValue}, selected`);
14835
+ }, announcementDelay);
14044
14836
  }
14045
14837
  });
14046
14838
 
@@ -14051,10 +14843,28 @@ class AuroCombobox extends AuroElement {
14051
14843
  this.menu.addEventListener('auroMenu-activatedOption', (evt) => {
14052
14844
  this.optionActive = evt.detail;
14053
14845
 
14846
+ if (this.input) {
14847
+ this.input.setActiveDescendant(this.optionActive);
14848
+ }
14849
+
14850
+ // Announce the active option for screen readers including position,
14851
+ // since shadow DOM boundaries prevent native reading of
14852
+ // aria-setsize/aria-posinset via aria-activedescendant.
14853
+ if (this.optionActive) {
14854
+ const optionText = this.optionActive.textContent.trim();
14855
+ const selectedState = this.optionActive.hasAttribute('selected') ? ', selected' : ', not selected';
14856
+ const optionIndex = this.availableOptions.indexOf(this.optionActive) + 1;
14857
+ const optionCount = this.availableOptions.length;
14858
+ announceToScreenReader(this.shadowRoot, `${optionText}${selectedState}, ${optionIndex} of ${optionCount}`);
14859
+ }
14860
+
14861
+ // Check if user prefers reduced motion for accessibility
14862
+ const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
14863
+
14054
14864
  this.optionActive.scrollIntoView({
14055
14865
  alignToTop: false,
14056
14866
  block: "nearest",
14057
- behavior: "smooth"
14867
+ behavior: prefersReducedMotion ? "auto" : "smooth"
14058
14868
  });
14059
14869
  });
14060
14870
 
@@ -14073,7 +14883,7 @@ class AuroCombobox extends AuroElement {
14073
14883
  * Validate every time we remove focus from the combo box.
14074
14884
  */
14075
14885
  this.addEventListener('focusout', () => {
14076
- if (!this.componentHasFocus) {
14886
+ if (!this.componentHasFocus && !this._inFullscreenTransition) {
14077
14887
  this.validate();
14078
14888
  }
14079
14889
  });
@@ -14109,8 +14919,33 @@ class AuroCombobox extends AuroElement {
14109
14919
  * @returns {void}
14110
14920
  */
14111
14921
  handleInputValueChange(event) {
14922
+ // When the event comes from the fullscreen bib input, sync the value to
14923
+ // the trigger input. Setting trigger.value triggers Lit's updated()
14924
+ // (async, microtask) which fires notifyValueChanged() → another 'input'
14925
+ // event from the trigger. The _syncingBibValue guard persists across the
14926
+ // async boundary and prevents that re-entrant event from running the
14927
+ // non-fullscreen path (which would call clear() → hideBib()).
14928
+ // When the event comes from the fullscreen bib input, sync the value to
14929
+ // the trigger and run filtering, but suppress the re-entrant input event
14930
+ // that the trigger fires (via Lit updated() → notifyValueChanged()) so
14931
+ // the non-fullscreen hide/clear logic doesn't close the dialog.
14112
14932
  if (event.target === this.inputInBib) {
14933
+ this._syncingBibValue = true;
14113
14934
  this.input.value = this.inputInBib.value;
14935
+ this.input.updateComplete.then(() => {
14936
+ this._syncingBibValue = false;
14937
+ });
14938
+
14939
+ // Run filtering inline — the re-entrant event won't reach this code.
14940
+ this.menu.matchWord = this.inputInBib.value;
14941
+ this.optionActive = null;
14942
+ this.handleMenuOptions();
14943
+ this.dispatchEvent(new CustomEvent('inputValue', { detail: { value: this.inputValue } }));
14944
+ return;
14945
+ }
14946
+
14947
+ // Ignore re-entrant input events caused by the bib→trigger sync above.
14948
+ if (this._syncingBibValue) {
14114
14949
  return;
14115
14950
  }
14116
14951
 
@@ -14124,8 +14959,11 @@ class AuroCombobox extends AuroElement {
14124
14959
  }
14125
14960
  this.handleMenuOptions();
14126
14961
 
14127
- // Validate only if the value was set programmatically
14128
- if (!this.componentHasFocus) {
14962
+ // Validate only if the value was set programmatically (not during user
14963
+ // interaction). In fullscreen dialog mode, componentHasFocus returns false
14964
+ // because focus is inside the top-layer dialog, so also check
14965
+ // dropdownOpen and the fullscreen transition flag.
14966
+ if (!this.componentHasFocus && !this.dropdownOpen && !this._inFullscreenTransition) {
14129
14967
  this.validate();
14130
14968
  }
14131
14969
 
@@ -14140,6 +14978,22 @@ class AuroCombobox extends AuroElement {
14140
14978
  this.hideBib();
14141
14979
  }
14142
14980
 
14981
+ // iOS virtual keyboard retention: when in fullscreen mode, ensure the
14982
+ // dialog opens and the bib input is focused synchronously within the
14983
+ // input event (user gesture) chain. Without this, Lit's async update
14984
+ // cycle delays showModal() past the user activation window, causing
14985
+ // iOS Safari to dismiss the virtual keyboard when the fullscreen
14986
+ // dialog opens — the user then has to tap the input again to resume
14987
+ // typing.
14988
+ if (this.dropdown.isBibFullscreen && this.input.value && this.input.value.length > 0) {
14989
+ if (!this.dropdown.isPopoverVisible) {
14990
+ this.showBib();
14991
+ }
14992
+ if (this.dropdown.isPopoverVisible) {
14993
+ this.setInputFocus();
14994
+ }
14995
+ }
14996
+
14143
14997
  this.dispatchEvent(new CustomEvent('inputValue', { detail: { value: this.inputValue } }));
14144
14998
  }
14145
14999
 
@@ -14149,61 +15003,7 @@ class AuroCombobox extends AuroElement {
14149
15003
  * @returns {void}
14150
15004
  */
14151
15005
  configureCombobox() {
14152
- this.addEventListener('keydown', async (evt) => {
14153
- if (evt.key === 'Enter') {
14154
- if (this.dropdown.isPopoverVisible && this.optionActive) {
14155
- this.menu.makeSelection();
14156
-
14157
- await this.updateComplete;
14158
-
14159
- evt.preventDefault();
14160
- evt.stopPropagation();
14161
- this.setClearBtnFocus();
14162
- } else {
14163
- this.showBib();
14164
- }
14165
- }
14166
-
14167
- if (evt.key === 'Tab' && this.dropdown.isPopoverVisible) {
14168
- if (this.dropdown.isBibFullscreen) {
14169
-
14170
- // when focus is on the input, move focus back to close button with Tab key
14171
- if (document.activeElement.shadowRoot.activeElement === this.inputInBib) {
14172
- evt.preventDefault();
14173
- this.dropdown.focus();
14174
- }
14175
- } else {
14176
- if (this.menu.optionActive && this.menu.optionActive.value) {
14177
- this.menu.value = this.menu.optionActive.value;
14178
- }
14179
-
14180
- setTimeout(() => {
14181
- if (!this.componentHasFocus) {
14182
- this.hideBib();
14183
- }
14184
- }, 0);
14185
- }
14186
- }
14187
-
14188
- /**
14189
- * Prevent moving the cursor position while navigating the menu options.
14190
- */
14191
- if (evt.key === 'ArrowUp' || evt.key === 'ArrowDown') {
14192
- if (this.availableOptions.length > 0) {
14193
- this.showBib();
14194
- }
14195
-
14196
- if (this.dropdown.isPopoverVisible) {
14197
- evt.preventDefault();
14198
-
14199
- // navigate on menu only when the focus is on input
14200
- if (!this.dropdown.isBibFullscreen || this.dropdown.isPopoverVisible) {
14201
- const direction = evt.key.replace('Arrow', '').toLowerCase();
14202
- this.menu.navigateOptions(direction);
14203
- }
14204
- }
14205
- }
14206
- });
15006
+ applyKeyboardStrategy(this, comboboxKeyboardStrategy);
14207
15007
 
14208
15008
  this.addEventListener('focusin', () => {
14209
15009
  this.touched = true;
@@ -14212,6 +15012,14 @@ class AuroCombobox extends AuroElement {
14212
15012
  });
14213
15013
 
14214
15014
  this.addEventListener('auroFormElement-validated', (evt) => {
15015
+ // During the fullscreen transition, child elements (auro-input) may fire
15016
+ // their own validation events when the trigger becomes inert and loses
15017
+ // focus. Those composed events bubble up through shadow DOM boundaries
15018
+ // and would incorrectly set combobox validity. Ignore them.
15019
+ if (this._inFullscreenTransition) {
15020
+ return;
15021
+ }
15022
+
14215
15023
  this.input.validity = evt.detail.validity;
14216
15024
  this.input.errorMessage = evt.detail.message;
14217
15025
  this.validity = evt.detail.validity;
@@ -14238,6 +15046,11 @@ class AuroCombobox extends AuroElement {
14238
15046
  }
14239
15047
  }
14240
15048
 
15049
+ disconnectedCallback() {
15050
+ super.disconnectedCallback();
15051
+ this._inFullscreenTransition = false;
15052
+ }
15053
+
14241
15054
  firstUpdated() {
14242
15055
  // Add the tag name as an attribute if it is different than the component name
14243
15056
  this.runtimeUtils.handleComponentTagRename(this, 'auro-combobox');
@@ -14315,6 +15128,9 @@ class AuroCombobox extends AuroElement {
14315
15128
  * @param {boolean} [force=false] - Whether to force validation.
14316
15129
  */
14317
15130
  validate(force = false) {
15131
+ if (this._inFullscreenTransition) {
15132
+ return;
15133
+ }
14318
15134
  this.validation.validate(this, force);
14319
15135
  }
14320
15136
 
@@ -14368,7 +15184,13 @@ class AuroCombobox extends AuroElement {
14368
15184
  }
14369
15185
 
14370
15186
  if (changedProperties.has('availableOptions')) {
14371
- if ((this.availableOptions.length > 0 && this.componentHasFocus) || this.menu.loading || (this.availableOptions.length === 0 && this.noMatchOption)) {
15187
+ // dropdownOpen is set synchronously by the auroDropdown-toggled event
15188
+ // handler during showBib() → floater.showBib() → dispatchEventDropdownToggle(),
15189
+ // so it's already true by the time updated() runs. This prevents the else
15190
+ // branch from calling hideBib() when the dropdown was just opened but
15191
+ // :focus-within hasn't propagated through the top-layer dialog's nested
15192
+ // shadow DOM boundaries.
15193
+ if ((this.availableOptions.length > 0 && (this.componentHasFocus || this.dropdownOpen)) || this.menu.loading || (this.availableOptions.length === 0 && this.noMatchOption)) {
14372
15194
  this.showBib();
14373
15195
  } else {
14374
15196
  this.hideBib();
@@ -14485,14 +15307,7 @@ class AuroCombobox extends AuroElement {
14485
15307
 
14486
15308
  return html`
14487
15309
  <div>
14488
- <div aria-live="polite" class="util_displayHiddenVisually">
14489
- ${this.optionActive && this.availableOptions.length > 0
14490
- ? html`
14491
- ${`${this.optionActive.textContent}, selected, ${this.availableOptions.indexOf(this.optionActive) + 1} of ${this.availableOptions.length}`}
14492
- `
14493
- : undefined
14494
- }
14495
- </div>
15310
+ <span id="srAnnouncement" class="util_displayHiddenVisually" aria-live="polite" role="status"></span>
14496
15311
  <${this.dropdownTag}
14497
15312
  appearance="${this.onDark ? 'inverse' : this.appearance}"
14498
15313
  .fullscreenBreakpoint="${this.fullscreenBreakpoint}"
@@ -14516,7 +15331,8 @@ class AuroCombobox extends AuroElement {
14516
15331
  <${this.inputTag}
14517
15332
  @input="${this.handleInputValueChange}"
14518
15333
  appearance="${this.onDark ? 'inverse' : this.appearance}"
14519
- .a11yExpanded="${this.dropdownOpen}"
15334
+ .a11yActivedescendant="${this.dropdownOpen && this.optionActive ? this.optionActive.id : undefined}"
15335
+ .a11yExpanded="${this.triggerExpandedState}"
14520
15336
  .a11yControls="${this.dropdownId}"
14521
15337
  .autocomplete="${this.autocomplete}"
14522
15338
  .inputmode="${this.inputmode}"
@@ -14549,8 +15365,10 @@ class AuroCombobox extends AuroElement {
14549
15365
  <slot @slotchange="${this.handleSlotChange}"></slot>
14550
15366
  <${this.inputTag}
14551
15367
  id="inputInBib"
15368
+ autofocus
14552
15369
  @input="${this.handleInputValueChange}"
14553
- .a11yControls="${this.dropdownId}"
15370
+ .a11yActivedescendant="${this.dropdownOpen && this.optionActive ? this.optionActive.id : undefined}"
15371
+ .a11yControls=${`${this.dropdownId}-floater-bib`}
14554
15372
  .autocomplete="${this.autocomplete}"
14555
15373
  .format="${this.format}"
14556
15374
  .inputmode="${this.inputmode}"
@@ -14561,7 +15379,7 @@ class AuroCombobox extends AuroElement {
14561
15379
  ?icon="${this.triggerIcon}"
14562
15380
  ?required="${this.required}"
14563
15381
  a11yRole="combobox"
14564
- a11yExpanded="true"
15382
+ .a11yExpanded="${this.dropdownOpen}"
14565
15383
  layout="classic"
14566
15384
  noValidate="true"
14567
15385
  shape="classic"