@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 { LitElement, css, html as html$1 } from 'lit';
2
2
  import { classMap } from 'lit/directives/class-map.js';
3
3
  import { unsafeStatic, literal, html } from 'lit/static-html.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 { ifDefined as ifDefined$1 } from 'lit-html/directives/if-defined.js';
@@ -952,7 +954,7 @@ class AuroFormValidation {
952
954
  }
953
955
  }
954
956
 
955
- if (!hasValue && elem.required && elem.touched) {
957
+ if (!hasValue && elem.required && (force || elem.touched)) {
956
958
  elem.validity = 'valueMissing';
957
959
  elem.errorMessage = elem.setCustomValidityValueMissing || elem.setCustomValidity || '';
958
960
  } else if (hasValue && this.runtimeUtils.elementMatch(elem, 'auro-input')) {
@@ -976,7 +978,7 @@ class AuroFormValidation {
976
978
  if (!isCombobox || isCombobox && !elem.persistInput) {
977
979
 
978
980
  // run validation on all inputs since we're going to use them to set the validity of this component
979
- this.auroInputElements.forEach(input => input.validate());
981
+ this.auroInputElements.forEach(input => input.validate(force));
980
982
 
981
983
  // Reset element validity to the validity of the input
982
984
  elem.validity = this.auroInputElements[0].validity;
@@ -1067,6 +1069,171 @@ class AuroFormValidation {
1067
1069
  }
1068
1070
  }
1069
1071
 
1072
+ /**
1073
+ * Announces text to screen readers via an `aria-live` region inside the given shadow root.
1074
+ *
1075
+ * Expects the shadow root to contain an element with `id="srAnnouncement"`.
1076
+ * The text is cleared and re-set inside a `requestAnimationFrame` so that
1077
+ * repeated identical announcements still fire, and is cleared again after
1078
+ * {@link ANNOUNCEMENT_DURATION_MS} so VoiceOver cannot swipe to stale text.
1079
+ *
1080
+ * @param {ShadowRoot} shadowRoot - The shadow root containing the live region.
1081
+ * @param {string} text - The text to announce.
1082
+ */
1083
+
1084
+ const ANNOUNCEMENT_DURATION_MS = 1000;
1085
+
1086
+ function announceToScreenReader(shadowRoot, text) {
1087
+ const liveRegion = shadowRoot.querySelector('#srAnnouncement');
1088
+ if (liveRegion) {
1089
+ // Clear and re-set to ensure the announcement fires even with same text
1090
+ liveRegion.textContent = '';
1091
+ requestAnimationFrame(() => {
1092
+ liveRegion.textContent = text;
1093
+
1094
+ // Clear after the announcement so VoiceOver cannot swipe to stale text
1095
+ setTimeout(() => {
1096
+ liveRegion.textContent = '';
1097
+ }, ANNOUNCEMENT_DURATION_MS);
1098
+ });
1099
+ }
1100
+ }
1101
+
1102
+ /**
1103
+ * Schedules a callback after two animation frames.
1104
+ *
1105
+ * Used when opening a fullscreen dialog to wait for the dialog to render
1106
+ * (first frame) and then for a Lit update cycle to complete (second frame)
1107
+ * before performing an action like focusing the close button.
1108
+ *
1109
+ * @param {Function} fn - The callback to execute after two animation frames.
1110
+ */
1111
+ function doubleRaf(fn) {
1112
+ requestAnimationFrame(() => {
1113
+ requestAnimationFrame(fn);
1114
+ });
1115
+ }
1116
+
1117
+ /**
1118
+ * Prevents touch pass-through when a fullscreen dialog opens on a touch device.
1119
+ *
1120
+ * On coarse-pointer devices (phones / tablets), the tap that opens the
1121
+ * fullscreen dialog can "pass through" to content beneath the finger —
1122
+ * the touchstart opens the dialog, but the finger is still on the screen,
1123
+ * so the subsequent touchend / click lands on whatever element sits at
1124
+ * those coordinates (e.g. a menu option or calendar cell), selecting it
1125
+ * unintentionally. This does NOT happen with mouse clicks because
1126
+ * showModal() promotes the dialog to the top layer synchronously and the
1127
+ * click has already completed.
1128
+ *
1129
+ * Guard: only activates on devices whose primary input is coarse.
1130
+ * Laptops with a touchscreen report `pointer: fine` (trackpad / mouse is
1131
+ * primary) so they are unaffected. Re-enables on the next touchstart,
1132
+ * which is the user's first deliberate gesture inside the dialog.
1133
+ *
1134
+ * @param {HTMLElement} element - The element to disable pointer events on
1135
+ * (e.g. the menu or calendar wrapper).
1136
+ */
1137
+ function guardTouchPassthrough(element) {
1138
+ if (!element || !window.matchMedia('(pointer: coarse)').matches) return;
1139
+
1140
+ element.style.pointerEvents = 'none';
1141
+ document.addEventListener('touchstart', () => {
1142
+ element.style.pointerEvents = '';
1143
+ }, { once: true });
1144
+ }
1145
+
1146
+ /**
1147
+ * Restores the dropdown trigger after a fullscreen dialog closes.
1148
+ *
1149
+ * Removes the `inert` attribute from the trigger so it is accessible again,
1150
+ * and restores focus to the given target after one animation frame. The rAF
1151
+ * delay lets Lit's microtask update cycle call `dialog.close()` first —
1152
+ * without it the browser's native dialog focus restoration can conflict.
1153
+ *
1154
+ * The focus is only applied if the dropdown is still closed at the time the
1155
+ * rAF fires, guarding against a rapid close-then-reopen race.
1156
+ *
1157
+ * @param {HTMLElement} dropdown - The `auro-dropdown` element.
1158
+ * @param {HTMLElement} focusTarget - The element to focus (e.g. trigger or input).
1159
+ */
1160
+ function restoreTriggerAfterClose(dropdown, focusTarget) {
1161
+ dropdown.trigger.inert = false;
1162
+
1163
+ if (dropdown.isBibFullscreen) {
1164
+ requestAnimationFrame(() => {
1165
+ if (!dropdown.isPopoverVisible) {
1166
+ focusTarget.focus();
1167
+ }
1168
+ });
1169
+ }
1170
+ }
1171
+
1172
+ /**
1173
+ * Wires up a keydown listener that dispatches to strategy[evt.key] or strategy.default.
1174
+ * Handles both sync and async handlers.
1175
+ * @param {HTMLElement} component - The component to attach the listener to.
1176
+ * @param {Object} strategy - Map of key names to handler functions.
1177
+ */
1178
+ function applyKeyboardStrategy(component, strategy) {
1179
+ component.addEventListener('keydown', async (evt) => {
1180
+ const handler = strategy[evt.key] || strategy.default;
1181
+ if (handler) {
1182
+ await handler(component, evt);
1183
+ }
1184
+ });
1185
+ }
1186
+
1187
+ /**
1188
+ * Shared arrow navigation. Calls menu.navigateOptions(direction) if visible.
1189
+ * Optionally opens dropdown via showFn when closed.
1190
+ * @param {HTMLElement} component - The component with dropdown and menu references.
1191
+ * @param {string} direction - 'up' or 'down'.
1192
+ * @param {Object} [options] - Optional config.
1193
+ * @param {Function} [options.showFn] - Called to open the dropdown when closed.
1194
+ */
1195
+ function navigateArrow(component, direction, options = {}) {
1196
+ if (component.dropdown.isPopoverVisible) {
1197
+ component.menu.navigateOptions(direction);
1198
+ } else if (options.showFn) {
1199
+ options.showFn();
1200
+ }
1201
+ }
1202
+
1203
+ const selectKeyboardStrategy = {
1204
+ ArrowUp(component, evt) {
1205
+ evt.preventDefault();
1206
+ navigateArrow(component, 'up', { showFn: () => component.dropdown.show() });
1207
+ },
1208
+
1209
+ ArrowDown(component, evt) {
1210
+ evt.preventDefault();
1211
+ navigateArrow(component, 'down', { showFn: () => component.dropdown.show() });
1212
+ },
1213
+
1214
+ Enter(component, evt) {
1215
+ evt.preventDefault();
1216
+ component.menu.makeSelection();
1217
+ },
1218
+
1219
+ Tab(component) {
1220
+ if (!component.dropdown.isPopoverVisible) {
1221
+ return;
1222
+ }
1223
+
1224
+ // Tab selects the focused option and closes the popup per the
1225
+ // WAI-ARIA APG select-only combobox / listbox pattern.
1226
+ if (component.optionActive && !component.multiSelect) {
1227
+ component.menu.makeSelection();
1228
+ }
1229
+ component.dropdown.hide();
1230
+ },
1231
+
1232
+ default(component, evt) {
1233
+ component.updateActiveOptionBasedOnKey(evt.key);
1234
+ },
1235
+ };
1236
+
1070
1237
  // Copyright (c) Alaska Air. All right reserved. Licensed under the Apache-2.0 license
1071
1238
  // See LICENSE in the project root for license information.
1072
1239
 
@@ -2834,11 +3001,9 @@ const computePosition = (reference, floating, options) => {
2834
3001
  /* eslint-disable line-comment-position, no-inline-comments */
2835
3002
 
2836
3003
 
2837
-
2838
3004
  const MAX_CONFIGURATION_COUNT = 10;
2839
3005
 
2840
3006
  class AuroFloatingUI {
2841
-
2842
3007
  /**
2843
3008
  * @private
2844
3009
  */
@@ -2853,7 +3018,11 @@ class AuroFloatingUI {
2853
3018
  * @private
2854
3019
  */
2855
3020
  static setupMousePressChecker() {
2856
- if (!AuroFloatingUI.isMousePressHandlerInitialized && window && window.addEventListener) {
3021
+ if (
3022
+ !AuroFloatingUI.isMousePressHandlerInitialized &&
3023
+ window &&
3024
+ window.addEventListener
3025
+ ) {
2857
3026
  AuroFloatingUI.isMousePressHandlerInitialized = true;
2858
3027
 
2859
3028
  // Track timeout for isMousePressed reset to avoid race conditions
@@ -2861,7 +3030,7 @@ class AuroFloatingUI {
2861
3030
  AuroFloatingUI._mousePressedTimeout = null;
2862
3031
  }
2863
3032
  const mouseEventGlobalHandler = (event) => {
2864
- const isPressed = event.type === 'mousedown';
3033
+ const isPressed = event.type === "mousedown";
2865
3034
  if (isPressed) {
2866
3035
  // Clear any pending timeout to prevent race condition
2867
3036
  if (AuroFloatingUI._mousePressedTimeout !== null) {
@@ -2880,8 +3049,8 @@ class AuroFloatingUI {
2880
3049
  }
2881
3050
  };
2882
3051
 
2883
- window.addEventListener('mousedown', mouseEventGlobalHandler);
2884
- window.addEventListener('mouseup', mouseEventGlobalHandler);
3052
+ window.addEventListener("mousedown", mouseEventGlobalHandler);
3053
+ window.addEventListener("mouseup", mouseEventGlobalHandler);
2885
3054
  }
2886
3055
  }
2887
3056
 
@@ -2929,11 +3098,12 @@ class AuroFloatingUI {
2929
3098
  // mirror the boxsize from bibSizer
2930
3099
  if (this.element.bibSizer && this.element.matchWidth) {
2931
3100
  const sizerStyle = window.getComputedStyle(this.element.bibSizer);
2932
- const bibContent = this.element.bib.shadowRoot.querySelector(".container");
2933
- if (sizerStyle.width !== '0px') {
3101
+ const bibContent =
3102
+ this.element.bib.shadowRoot.querySelector(".container");
3103
+ if (sizerStyle.width !== "0px") {
2934
3104
  bibContent.style.width = sizerStyle.width;
2935
3105
  }
2936
- if (sizerStyle.height !== '0px') {
3106
+ if (sizerStyle.height !== "0px") {
2937
3107
  bibContent.style.height = sizerStyle.height;
2938
3108
  }
2939
3109
  bibContent.style.maxWidth = sizerStyle.maxWidth;
@@ -2951,28 +3121,34 @@ class AuroFloatingUI {
2951
3121
  * @returns {String} The positioning strategy, one of 'fullscreen', 'floating', 'cover'.
2952
3122
  */
2953
3123
  getPositioningStrategy() {
2954
- const breakpoint = this.element.bib.mobileFullscreenBreakpoint || this.element.floaterConfig?.fullscreenBreakpoint;
3124
+ const breakpoint =
3125
+ this.element.bib.mobileFullscreenBreakpoint ||
3126
+ this.element.floaterConfig?.fullscreenBreakpoint;
2955
3127
  switch (this.behavior) {
2956
3128
  case "tooltip":
2957
3129
  return "floating";
2958
3130
  case "dialog":
2959
3131
  case "drawer":
2960
3132
  if (breakpoint) {
2961
- const smallerThanBreakpoint = window.matchMedia(`(max-width: ${breakpoint})`).matches;
3133
+ const smallerThanBreakpoint = window.matchMedia(
3134
+ `(max-width: ${breakpoint})`,
3135
+ ).matches;
2962
3136
 
2963
3137
  this.element.expanded = smallerThanBreakpoint;
2964
3138
  }
2965
3139
  if (this.element.nested) {
2966
3140
  return "cover";
2967
3141
  }
2968
- return 'fullscreen';
3142
+ return "fullscreen";
2969
3143
  case "dropdown":
2970
3144
  case undefined:
2971
3145
  case null:
2972
3146
  if (breakpoint) {
2973
- const smallerThanBreakpoint = window.matchMedia(`(max-width: ${breakpoint})`).matches;
3147
+ const smallerThanBreakpoint = window.matchMedia(
3148
+ `(max-width: ${breakpoint})`,
3149
+ ).matches;
2974
3150
  if (smallerThanBreakpoint) {
2975
- return 'fullscreen';
3151
+ return "fullscreen";
2976
3152
  }
2977
3153
  }
2978
3154
  return "floating";
@@ -2993,37 +3169,39 @@ class AuroFloatingUI {
2993
3169
  const strategy = this.getPositioningStrategy();
2994
3170
  this.configureBibStrategy(strategy);
2995
3171
 
2996
- if (strategy === 'floating') {
3172
+ if (strategy === "floating") {
2997
3173
  this.mirrorSize();
2998
3174
  // Define the middlware for the floater configuration
2999
3175
  const middleware = [
3000
3176
  offset(this.element.floaterConfig?.offset || 0),
3001
- ...this.element.floaterConfig?.shift ? [shift()] : [], // Add shift middleware if shift is enabled.
3002
- ...this.element.floaterConfig?.flip ? [flip()] : [], // Add flip middleware if flip is enabled.
3003
- ...this.element.floaterConfig?.autoPlacement ? [autoPlacement()] : [], // Add autoPlacement middleware if autoPlacement is enabled.
3177
+ ...(this.element.floaterConfig?.shift ? [shift()] : []), // Add shift middleware if shift is enabled.
3178
+ ...(this.element.floaterConfig?.flip ? [flip()] : []), // Add flip middleware if flip is enabled.
3179
+ ...(this.element.floaterConfig?.autoPlacement ? [autoPlacement()] : []), // Add autoPlacement middleware if autoPlacement is enabled.
3004
3180
  ];
3005
3181
 
3006
3182
  // Compute the position of the bib
3007
3183
  computePosition(this.element.trigger, this.element.bib, {
3008
- strategy: this.element.floaterConfig?.strategy || 'fixed',
3184
+ strategy: this.element.floaterConfig?.strategy || "fixed",
3009
3185
  placement: this.element.floaterConfig?.placement,
3010
- middleware: middleware || []
3011
- }).then(({ x, y }) => { // eslint-disable-line id-length
3186
+ middleware: middleware || [],
3187
+ }).then(({ x, y }) => {
3188
+ // eslint-disable-line id-length
3012
3189
  Object.assign(this.element.bib.style, {
3013
3190
  left: `${x}px`,
3014
3191
  top: `${y}px`,
3015
3192
  });
3016
3193
  });
3017
- } else if (strategy === 'cover') {
3194
+ } else if (strategy === "cover") {
3018
3195
  // Compute the position of the bib
3019
3196
  computePosition(this.element.parentNode, this.element.bib, {
3020
- placement: 'bottom-start'
3021
- }).then(({ x, y }) => { // eslint-disable-line id-length
3197
+ placement: "bottom-start",
3198
+ }).then(({ x, y }) => {
3199
+ // eslint-disable-line id-length
3022
3200
  Object.assign(this.element.bib.style, {
3023
3201
  left: `${x}px`,
3024
3202
  top: `${y - this.element.parentNode.offsetHeight}px`,
3025
3203
  width: `${this.element.parentNode.offsetWidth}px`,
3026
- height: `${this.element.parentNode.offsetHeight}px`
3204
+ height: `${this.element.parentNode.offsetHeight}px`,
3027
3205
  });
3028
3206
  });
3029
3207
  }
@@ -3036,12 +3214,12 @@ class AuroFloatingUI {
3036
3214
  */
3037
3215
  lockScroll(lock = true) {
3038
3216
  if (lock) {
3039
- document.body.style.overflow = 'hidden'; // hide body's scrollbar
3217
+ document.body.style.overflow = "hidden"; // hide body's scrollbar
3040
3218
 
3041
3219
  // Move `bib` by the amount the viewport is shifted to stay aligned in fullscreen.
3042
3220
  this.element.bib.style.transform = `translateY(${window?.visualViewport?.offsetTop}px)`;
3043
3221
  } else {
3044
- document.body.style.overflow = '';
3222
+ document.body.style.overflow = "";
3045
3223
  }
3046
3224
  }
3047
3225
 
@@ -3055,23 +3233,24 @@ class AuroFloatingUI {
3055
3233
  * @param {string} strategy - The positioning strategy ('fullscreen' or 'floating').
3056
3234
  */
3057
3235
  configureBibStrategy(value) {
3058
- if (value === 'fullscreen') {
3236
+ if (value === "fullscreen") {
3059
3237
  this.element.isBibFullscreen = true;
3060
3238
  // reset the prev position
3061
- this.element.bib.setAttribute('isfullscreen', "");
3062
- this.element.bib.style.position = 'fixed';
3239
+ this.element.bib.setAttribute("isfullscreen", "");
3240
+ this.element.bib.style.position = "fixed";
3063
3241
  this.element.bib.style.top = "0px";
3064
3242
  this.element.bib.style.left = "0px";
3065
- this.element.bib.style.width = '';
3066
- this.element.bib.style.height = '';
3067
- this.element.style.contain = '';
3243
+ this.element.bib.style.width = "";
3244
+ this.element.bib.style.height = "";
3245
+ this.element.style.contain = "";
3068
3246
 
3069
3247
  // reset the size that was mirroring `size` css-part
3070
- const bibContent = this.element.bib.shadowRoot.querySelector(".container");
3248
+ const bibContent =
3249
+ this.element.bib.shadowRoot.querySelector(".container");
3071
3250
  if (bibContent) {
3072
- bibContent.style.width = '';
3073
- bibContent.style.height = '';
3074
- bibContent.style.maxWidth = '';
3251
+ bibContent.style.width = "";
3252
+ bibContent.style.height = "";
3253
+ bibContent.style.maxWidth = "";
3075
3254
  bibContent.style.maxHeight = `${window?.visualViewport?.height}px`;
3076
3255
  this.configureTrial = 0;
3077
3256
  } else if (this.configureTrial < MAX_CONFIGURATION_COUNT) {
@@ -3086,21 +3265,26 @@ class AuroFloatingUI {
3086
3265
  this.lockScroll(true);
3087
3266
  }
3088
3267
  } else {
3089
- this.element.bib.style.position = '';
3090
- this.element.bib.removeAttribute('isfullscreen');
3268
+ this.element.bib.style.position = "";
3269
+ this.element.bib.removeAttribute("isfullscreen");
3091
3270
  this.element.isBibFullscreen = false;
3092
- this.element.style.contain = 'layout';
3271
+ this.element.style.contain = "layout";
3093
3272
  }
3094
3273
 
3095
3274
  const isChanged = this.strategy && this.strategy !== value;
3096
3275
  this.strategy = value;
3097
3276
  if (isChanged) {
3098
- const event = new CustomEvent(this.eventPrefix ? `${this.eventPrefix}-strategy-change` : 'strategy-change', {
3099
- detail: {
3100
- value,
3277
+ const event = new CustomEvent(
3278
+ this.eventPrefix
3279
+ ? `${this.eventPrefix}-strategy-change`
3280
+ : "strategy-change",
3281
+ {
3282
+ detail: {
3283
+ value,
3284
+ },
3285
+ composed: true,
3101
3286
  },
3102
- composed: true
3103
- });
3287
+ );
3104
3288
 
3105
3289
  this.element.dispatchEvent(event);
3106
3290
  }
@@ -3132,19 +3316,24 @@ class AuroFloatingUI {
3132
3316
  return;
3133
3317
  }
3134
3318
 
3135
- if (this.element.noHideOnThisFocusLoss ||
3136
- this.element.hasAttribute('noHideOnThisFocusLoss')) {
3319
+ if (
3320
+ this.element.noHideOnThisFocusLoss ||
3321
+ this.element.hasAttribute("noHideOnThisFocusLoss")
3322
+ ) {
3137
3323
  return;
3138
3324
  }
3139
3325
 
3140
3326
  const { activeElement } = document;
3141
3327
  // if focus is still inside of trigger or bib, do not close
3142
- if (this.element.contains(activeElement) || this.element.bib?.contains(activeElement)) {
3328
+ if (
3329
+ this.element.contains(activeElement) ||
3330
+ this.element.bib?.contains(activeElement)
3331
+ ) {
3143
3332
  return;
3144
3333
  }
3145
3334
 
3146
3335
  // if fullscreen bib is in fullscreen mode, do not close
3147
- if (this.element.bib.hasAttribute('isfullscreen')) {
3336
+ if (this.element.bib.hasAttribute("isfullscreen")) {
3148
3337
  return;
3149
3338
  }
3150
3339
 
@@ -3156,12 +3345,27 @@ class AuroFloatingUI {
3156
3345
  this.focusHandler = () => this.handleFocusLoss();
3157
3346
 
3158
3347
  this.clickHandler = (evt) => {
3159
- if ((!evt.composedPath().includes(this.element.trigger) &&
3160
- !evt.composedPath().includes(this.element.bib)) ||
3161
- (this.element.bib.backdrop && evt.composedPath().includes(this.element.bib.backdrop))) {
3162
- const existedVisibleFloatingUI = document.expandedAuroFormkitDropdown || document.expandedAuroFloater;
3348
+ // When the bib is fullscreen (modal dialog), don't close on outside
3349
+ // clicks. VoiceOver's synthetic click events inside a top-layer modal
3350
+ // <dialog> may not include the bib in composedPath(), causing false
3351
+ // positives. This mirrors the fullscreen guard in handleFocusLoss().
3352
+ if (this.element.bib && this.element.bib.hasAttribute("isfullscreen")) {
3353
+ return;
3354
+ }
3163
3355
 
3164
- if (existedVisibleFloatingUI && existedVisibleFloatingUI.element.isPopoverVisible) {
3356
+ if (
3357
+ (!evt.composedPath().includes(this.element.trigger) &&
3358
+ !evt.composedPath().includes(this.element.bib)) ||
3359
+ (this.element.bib.backdrop &&
3360
+ evt.composedPath().includes(this.element.bib.backdrop))
3361
+ ) {
3362
+ const existedVisibleFloatingUI =
3363
+ document.expandedAuroFormkitDropdown || document.expandedAuroFloater;
3364
+
3365
+ if (
3366
+ existedVisibleFloatingUI &&
3367
+ existedVisibleFloatingUI.element.isPopoverVisible
3368
+ ) {
3165
3369
  // if something else is open, close that
3166
3370
  existedVisibleFloatingUI.hideBib();
3167
3371
  document.expandedAuroFormkitDropdown = null;
@@ -3174,9 +3378,14 @@ class AuroFloatingUI {
3174
3378
 
3175
3379
  // ESC key handler
3176
3380
  this.keyDownHandler = (evt) => {
3177
- if (evt.key === 'Escape' && this.element.isPopoverVisible) {
3178
- const existedVisibleFloatingUI = document.expandedAuroFormkitDropdown || document.expandedAuroFloater;
3179
- if (existedVisibleFloatingUI && existedVisibleFloatingUI !== this && existedVisibleFloatingUI.element.isPopoverVisible) {
3381
+ if (evt.key === "Escape" && this.element.isPopoverVisible) {
3382
+ const existedVisibleFloatingUI =
3383
+ document.expandedAuroFormkitDropdown || document.expandedAuroFloater;
3384
+ if (
3385
+ existedVisibleFloatingUI &&
3386
+ existedVisibleFloatingUI !== this &&
3387
+ existedVisibleFloatingUI.element.isPopoverVisible
3388
+ ) {
3180
3389
  // if something else is open, let it handle itself
3181
3390
  return;
3182
3391
  }
@@ -3184,17 +3393,17 @@ class AuroFloatingUI {
3184
3393
  }
3185
3394
  };
3186
3395
 
3187
- if (this.behavior !== 'drawer' && this.behavior !== 'dialog') {
3396
+ if (this.behavior !== "drawer" && this.behavior !== "dialog") {
3188
3397
  // Add event listeners using the stored references
3189
- document.addEventListener('focusin', this.focusHandler);
3398
+ document.addEventListener("focusin", this.focusHandler);
3190
3399
  }
3191
3400
 
3192
- document.addEventListener('keydown', this.keyDownHandler);
3401
+ document.addEventListener("keydown", this.keyDownHandler);
3193
3402
 
3194
3403
  // send this task to the end of queue to prevent conflicting
3195
3404
  // it conflicts if showBib gets call from a button that's not this.element.trigger
3196
3405
  setTimeout(() => {
3197
- window.addEventListener('click', this.clickHandler);
3406
+ window.addEventListener("click", this.clickHandler);
3198
3407
  }, 0);
3199
3408
  }
3200
3409
 
@@ -3202,34 +3411,38 @@ class AuroFloatingUI {
3202
3411
  // Remove event listeners if they exist
3203
3412
 
3204
3413
  if (this.focusHandler) {
3205
- document.removeEventListener('focusin', this.focusHandler);
3414
+ document.removeEventListener("focusin", this.focusHandler);
3206
3415
  this.focusHandler = null;
3207
3416
  }
3208
3417
 
3209
3418
  if (this.clickHandler) {
3210
- window.removeEventListener('click', this.clickHandler);
3419
+ window.removeEventListener("click", this.clickHandler);
3211
3420
  this.clickHandler = null;
3212
3421
  }
3213
3422
 
3214
3423
  if (this.keyDownHandler) {
3215
- document.removeEventListener('keydown', this.keyDownHandler);
3424
+ document.removeEventListener("keydown", this.keyDownHandler);
3216
3425
  this.keyDownHandler = null;
3217
3426
  }
3218
3427
  }
3219
3428
 
3220
3429
  handleUpdate(changedProperties) {
3221
- if (changedProperties.has('isPopoverVisible')) {
3430
+ if (changedProperties.has("isPopoverVisible")) {
3222
3431
  this.updateState();
3223
3432
  }
3224
3433
  }
3225
3434
 
3226
3435
  updateCurrentExpandedDropdown() {
3227
3436
  // Close any other dropdown that is already open
3228
- const existedVisibleFloatingUI = document.expandedAuroFormkitDropdown || document.expandedAuroFloater;
3229
- if (existedVisibleFloatingUI && existedVisibleFloatingUI !== this &&
3437
+ const existedVisibleFloatingUI =
3438
+ document.expandedAuroFormkitDropdown || document.expandedAuroFloater;
3439
+ if (
3440
+ existedVisibleFloatingUI &&
3441
+ existedVisibleFloatingUI !== this &&
3230
3442
  existedVisibleFloatingUI.element.isPopoverVisible &&
3231
- document.expandedAuroFloater.eventPrefix === this.eventPrefix) {
3232
- document.expandedAuroFloater.hideBib();
3443
+ existedVisibleFloatingUI.eventPrefix === this.eventPrefix
3444
+ ) {
3445
+ existedVisibleFloatingUI.hideBib();
3233
3446
  }
3234
3447
 
3235
3448
  document.expandedAuroFloater = this;
@@ -3238,7 +3451,7 @@ class AuroFloatingUI {
3238
3451
  showBib() {
3239
3452
  if (!this.element.disabled && !this.showing) {
3240
3453
  this.updateCurrentExpandedDropdown();
3241
- this.element.triggerChevron?.setAttribute('data-expanded', true);
3454
+ this.element.triggerChevron?.setAttribute("data-expanded", true);
3242
3455
 
3243
3456
  // prevent double showing: isPopovervisible gets first and showBib gets called later
3244
3457
  if (!this.showing) {
@@ -3252,9 +3465,13 @@ class AuroFloatingUI {
3252
3465
  }
3253
3466
 
3254
3467
  // Setup auto update to handle resize and scroll
3255
- this.element.cleanup = autoUpdate(this.element.trigger || this.element.parentNode, this.element.bib, () => {
3256
- this.position();
3257
- });
3468
+ this.element.cleanup = autoUpdate(
3469
+ this.element.trigger || this.element.parentNode,
3470
+ this.element.bib,
3471
+ () => {
3472
+ this.position();
3473
+ },
3474
+ );
3258
3475
  }
3259
3476
  }
3260
3477
 
@@ -3265,7 +3482,7 @@ class AuroFloatingUI {
3265
3482
  hideBib(eventType = "unknown") {
3266
3483
  if (!this.element.disabled && !this.element.noToggle) {
3267
3484
  this.lockScroll(false);
3268
- this.element.triggerChevron?.removeAttribute('data-expanded');
3485
+ this.element.triggerChevron?.removeAttribute("data-expanded");
3269
3486
 
3270
3487
  if (this.element.isPopoverVisible) {
3271
3488
  this.element.isPopoverVisible = false;
@@ -3285,13 +3502,16 @@ class AuroFloatingUI {
3285
3502
  * @param {String} eventType - The event type that triggered the toggle action.
3286
3503
  */
3287
3504
  dispatchEventDropdownToggle(eventType) {
3288
- const event = new CustomEvent(this.eventPrefix ? `${this.eventPrefix}-toggled` : 'toggled', {
3289
- detail: {
3290
- expanded: this.showing,
3291
- eventType: eventType || "unknown",
3505
+ const event = new CustomEvent(
3506
+ this.eventPrefix ? `${this.eventPrefix}-toggled` : "toggled",
3507
+ {
3508
+ detail: {
3509
+ expanded: this.showing,
3510
+ eventType: eventType || "unknown",
3511
+ },
3512
+ composed: true,
3292
3513
  },
3293
- composed: true
3294
- });
3514
+ );
3295
3515
 
3296
3516
  this.element.dispatchEvent(event);
3297
3517
  }
@@ -3303,12 +3523,15 @@ class AuroFloatingUI {
3303
3523
  this.showBib();
3304
3524
  }
3305
3525
 
3306
- const event = new CustomEvent(this.eventPrefix ? `${this.eventPrefix}-triggerClick` : "triggerClick", {
3307
- composed: true,
3308
- detail: {
3309
- expanded: this.element.isPopoverVisible
3310
- }
3311
- });
3526
+ const event = new CustomEvent(
3527
+ this.eventPrefix ? `${this.eventPrefix}-triggerClick` : "triggerClick",
3528
+ {
3529
+ composed: true,
3530
+ detail: {
3531
+ expanded: this.element.isPopoverVisible,
3532
+ },
3533
+ },
3534
+ );
3312
3535
 
3313
3536
  this.element.dispatchEvent(event);
3314
3537
  }
@@ -3316,30 +3539,32 @@ class AuroFloatingUI {
3316
3539
  handleEvent(event) {
3317
3540
  if (!this.element.disableEventShow) {
3318
3541
  switch (event.type) {
3319
- case 'keydown':
3542
+ case "keydown": {
3320
3543
  // Support both Enter and Space keys for accessibility
3321
3544
  // Space is included as it's expected behavior for interactive elements
3322
3545
 
3323
3546
  const origin = event.composedPath()[0];
3324
- if (event.key === 'Enter' || event.key === ' ' && (!origin || origin.tagName !== "INPUT")) {
3325
-
3547
+ if (
3548
+ event.key === "Enter" ||
3549
+ (event.key === " " && (!origin || origin.tagName !== "INPUT"))
3550
+ ) {
3326
3551
  event.preventDefault();
3327
3552
  this.handleClick();
3328
3553
  }
3329
3554
  break;
3330
- case 'mouseenter':
3555
+ }
3556
+ case "mouseenter":
3331
3557
  if (this.element.hoverToggle) {
3332
3558
  this.showBib();
3333
3559
  }
3334
3560
  break;
3335
- case 'mouseleave':
3561
+ case "mouseleave":
3336
3562
  if (this.element.hoverToggle) {
3337
3563
  this.hideBib("mouseleave");
3338
3564
  }
3339
3565
  break;
3340
- case 'focus':
3566
+ case "focus":
3341
3567
  if (this.element.focusShow) {
3342
-
3343
3568
  /*
3344
3569
  This needs to better handle clicking that gives focus -
3345
3570
  currently it shows and then immediately hides the bib
@@ -3347,12 +3572,12 @@ class AuroFloatingUI {
3347
3572
  this.showBib();
3348
3573
  }
3349
3574
  break;
3350
- case 'blur':
3575
+ case "blur":
3351
3576
  // send this task 100ms later queue to
3352
3577
  // wait a frame in case focus moves within the floating element/bib
3353
3578
  setTimeout(() => this.handleFocusLoss(), 0);
3354
3579
  break;
3355
- case 'click':
3580
+ case "click":
3356
3581
  if (document.activeElement === document.body) {
3357
3582
  event.currentTarget.focus();
3358
3583
  }
@@ -3371,15 +3596,15 @@ class AuroFloatingUI {
3371
3596
  */
3372
3597
  handleTriggerTabIndex() {
3373
3598
  const focusableElementSelectors = [
3374
- 'a',
3375
- 'button',
3599
+ "a",
3600
+ "button",
3376
3601
  'input:not([type="hidden"])',
3377
- 'select',
3378
- 'textarea',
3602
+ "select",
3603
+ "textarea",
3379
3604
  '[tabindex]:not([tabindex="-1"])',
3380
- 'auro-button',
3381
- 'auro-input',
3382
- 'auro-hyperlink'
3605
+ "auro-button",
3606
+ "auro-input",
3607
+ "auro-hyperlink",
3383
3608
  ];
3384
3609
 
3385
3610
  const triggerNode = this.element.querySelectorAll('[slot="trigger"]')[0];
@@ -3407,10 +3632,10 @@ class AuroFloatingUI {
3407
3632
  * @param {*} eventPrefix
3408
3633
  */
3409
3634
  regenerateBibId() {
3410
- this.id = this.element.getAttribute('id');
3635
+ this.id = this.element.getAttribute("id");
3411
3636
  if (!this.id) {
3412
3637
  this.id = window.crypto.randomUUID();
3413
- this.element.setAttribute('id', this.id);
3638
+ this.element.setAttribute("id", this.id);
3414
3639
  }
3415
3640
 
3416
3641
  this.element.bib.setAttribute("id", `${this.id}-floater-bib`);
@@ -3431,11 +3656,15 @@ class AuroFloatingUI {
3431
3656
  if (this.element.trigger) {
3432
3657
  this.disconnect();
3433
3658
  }
3434
- this.element.trigger = this.element.triggerElement || this.element.shadowRoot.querySelector('#trigger') || this.element.trigger;
3435
- this.element.bib = this.element.shadowRoot.querySelector('#bib') || this.element.bib;
3436
- this.element.bibSizer = this.element.shadowRoot.querySelector('#bibSizer');
3437
- this.element.triggerChevron = this.element.shadowRoot.querySelector('#showStateIcon');
3438
-
3659
+ this.element.trigger =
3660
+ this.element.triggerElement ||
3661
+ this.element.shadowRoot.querySelector("#trigger") ||
3662
+ this.element.trigger;
3663
+ this.element.bib =
3664
+ this.element.shadowRoot.querySelector("#bib") || this.element.bib;
3665
+ this.element.bibSizer = this.element.shadowRoot.querySelector("#bibSizer");
3666
+ this.element.triggerChevron =
3667
+ this.element.shadowRoot.querySelector("#showStateIcon");
3439
3668
 
3440
3669
  if (this.element.floaterConfig) {
3441
3670
  this.element.hoverToggle = this.element.floaterConfig.hoverToggle;
@@ -3446,12 +3675,12 @@ class AuroFloatingUI {
3446
3675
 
3447
3676
  this.handleEvent = this.handleEvent.bind(this);
3448
3677
  if (this.element.trigger) {
3449
- this.element.trigger.addEventListener('keydown', this.handleEvent);
3450
- this.element.trigger.addEventListener('click', this.handleEvent);
3451
- this.element.trigger.addEventListener('mouseenter', this.handleEvent);
3452
- this.element.trigger.addEventListener('mouseleave', this.handleEvent);
3453
- this.element.trigger.addEventListener('focus', this.handleEvent);
3454
- this.element.trigger.addEventListener('blur', this.handleEvent);
3678
+ this.element.trigger.addEventListener("keydown", this.handleEvent);
3679
+ this.element.trigger.addEventListener("click", this.handleEvent);
3680
+ this.element.trigger.addEventListener("mouseenter", this.handleEvent);
3681
+ this.element.trigger.addEventListener("mouseleave", this.handleEvent);
3682
+ this.element.trigger.addEventListener("focus", this.handleEvent);
3683
+ this.element.trigger.addEventListener("blur", this.handleEvent);
3455
3684
  }
3456
3685
  }
3457
3686
 
@@ -3466,12 +3695,18 @@ class AuroFloatingUI {
3466
3695
 
3467
3696
  // Remove event & keyboard listeners
3468
3697
  if (this.element?.trigger) {
3469
- this.element.trigger.removeEventListener('keydown', this.handleEvent);
3470
- this.element.trigger.removeEventListener('click', this.handleEvent);
3471
- this.element.trigger.removeEventListener('mouseenter', this.handleEvent);
3472
- this.element.trigger.removeEventListener('mouseleave', this.handleEvent);
3473
- this.element.trigger.removeEventListener('focus', this.handleEvent);
3474
- this.element.trigger.removeEventListener('blur', this.handleEvent);
3698
+ this.element.trigger.removeEventListener("keydown", this.handleEvent);
3699
+ this.element.trigger.removeEventListener("click", this.handleEvent);
3700
+ this.element.trigger.removeEventListener(
3701
+ "mouseenter",
3702
+ this.handleEvent,
3703
+ );
3704
+ this.element.trigger.removeEventListener(
3705
+ "mouseleave",
3706
+ this.handleEvent,
3707
+ );
3708
+ this.element.trigger.removeEventListener("focus", this.handleEvent);
3709
+ this.element.trigger.removeEventListener("blur", this.handleEvent);
3475
3710
  }
3476
3711
  }
3477
3712
  }
@@ -3918,7 +4153,7 @@ let p$2 = class p{registerComponent(t,a){customElements.get(t)||customElements.d
3918
4153
 
3919
4154
  var iconVersion$1 = '9.1.2';
3920
4155
 
3921
- 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}`;
4156
+ 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}`;
3922
4157
 
3923
4158
  var colorCss$2 = 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)}`;
3924
4159
 
@@ -3926,6 +4161,8 @@ var tokensCss$1 = css`:host(:not([ondark])),:host(:not([appearance=inverse])){--
3926
4161
 
3927
4162
  // Copyright (c) 2020 Alaska Airlines. All right reserved. Licensed under the Apache-2.0 license
3928
4163
  // See LICENSE in the project root for license information.
4164
+ /* eslint-disable max-lines */
4165
+ // ---------------------------------------------------------------------
3929
4166
 
3930
4167
 
3931
4168
  const DESIGN_TOKEN_BREAKPOINT_PREFIX = '--ds-grid-breakpoint-';
@@ -4020,6 +4257,28 @@ class AuroDropdownBib extends LitElement {
4020
4257
  shape: {
4021
4258
  type: String,
4022
4259
  reflect: true
4260
+ },
4261
+
4262
+ /**
4263
+ * Accessible label for the dialog element, used when displayed as a modal.
4264
+ * Applied via aria-labelledby on a visually hidden element rather than
4265
+ * aria-label because iOS VoiceOver does not reliably read aria-label
4266
+ * on <dialog> elements.
4267
+ * @private
4268
+ */
4269
+ dialogLabel: {
4270
+ type: String
4271
+ },
4272
+
4273
+ /**
4274
+ * Overrides the native role of the dialog element.
4275
+ * For example, set to `"presentation"` on desktop combobox to prevent
4276
+ * VoiceOver from announcing "listbox inside of a dialog".
4277
+ * When `undefined`, the dialog keeps its native role.
4278
+ * @private
4279
+ */
4280
+ dialogRole: {
4281
+ type: String
4023
4282
  }
4024
4283
  };
4025
4284
  }
@@ -4087,7 +4346,10 @@ class AuroDropdownBib extends LitElement {
4087
4346
  firstUpdated(changedProperties) {
4088
4347
  super.firstUpdated(changedProperties);
4089
4348
 
4090
- // Dispatch a custom event when the component is connected
4349
+ const dialog = this.shadowRoot.querySelector('dialog');
4350
+ this._setupCancelHandler(dialog);
4351
+ this._setupKeyboardBridge(dialog);
4352
+
4091
4353
  this.dispatchEvent(new CustomEvent('auro-dropdownbib-connected', {
4092
4354
  bubbles: true,
4093
4355
  composed: true,
@@ -4097,6 +4359,189 @@ class AuroDropdownBib extends LitElement {
4097
4359
  }));
4098
4360
  }
4099
4361
 
4362
+ /**
4363
+ * Forwards the dialog's native `cancel` event (fired on ESC) as
4364
+ * an `auro-bib-cancel` custom event so parent components can close.
4365
+ * @param {HTMLDialogElement} dialog
4366
+ * @private
4367
+ */
4368
+ _setupCancelHandler(dialog) {
4369
+ dialog.addEventListener('cancel', (event) => {
4370
+ event.preventDefault();
4371
+ this.dispatchEvent(new CustomEvent('auro-bib-cancel', {
4372
+ bubbles: true,
4373
+ composed: true
4374
+ }));
4375
+ });
4376
+ }
4377
+
4378
+ /**
4379
+ * showModal() creates a closed focus scope — keyboard events inside
4380
+ * the dialog's shadow DOM do NOT bubble out to the combobox/select
4381
+ * keydown handlers in the parent shadow DOM. This handler bridges
4382
+ * that gap by re-dispatching navigation keys so they cross the
4383
+ * shadow boundary and reach the menu navigation logic in the parent
4384
+ * component.
4385
+ *
4386
+ * The trade-off: intercepting these keys means native keyboard
4387
+ * behaviors that would normally "just work" must be manually
4388
+ * re-implemented here:
4389
+ *
4390
+ * - Enter on buttons: Custom elements (auro-button) don't get the
4391
+ * native Enter→click that <button> provides, so we call .click()
4392
+ * directly when Enter is pressed on a button-like element.
4393
+ *
4394
+ * - Tab: Intercepted and re-dispatched so parent components
4395
+ * (select/combobox) can select the active option and close the
4396
+ * dialog. The <dialog> provides containment and isolation
4397
+ * (inert background, VoiceOver focus trapping, top layer), while
4398
+ * the content inside is a role="listbox" navigated via
4399
+ * aria-activedescendant (options are not focusable). Tab keyboard
4400
+ * behavior follows listbox conventions (select + close) because
4401
+ * the dialog's native Tab trap only cycles between the close
4402
+ * button and browser chrome.
4403
+ *
4404
+ * - Escape: The native <dialog> fires a `cancel` event on ESC
4405
+ * (handled by _setupCancelHandler), so the re-dispatched Escape
4406
+ * is a secondary path for parent components that also listen for
4407
+ * Escape keydown.
4408
+ *
4409
+ * @param {HTMLDialogElement} dialog
4410
+ * @private
4411
+ */
4412
+ _setupKeyboardBridge(dialog) {
4413
+ const navKeys = new Set([
4414
+ 'ArrowUp',
4415
+ 'ArrowDown',
4416
+ 'Enter',
4417
+ 'Escape',
4418
+ 'Tab'
4419
+ ]);
4420
+
4421
+ dialog.addEventListener('keydown', (event) => {
4422
+ if (!navKeys.has(event.key)) {
4423
+ return;
4424
+ }
4425
+
4426
+ // Custom elements (auro-button) don't get the native Enter→click
4427
+ // behavior that <button> has. Find the button in the composed path
4428
+ // and click it directly.
4429
+ if (event.key === 'Enter') {
4430
+ const buttonSelector = 'button, [role="button"], auro-button, [auro-button]';
4431
+ const btn = event.composedPath().find((el) => el.matches && el.matches(buttonSelector));
4432
+ if (btn) {
4433
+ event.preventDefault();
4434
+ event.stopPropagation();
4435
+ btn.click();
4436
+ return;
4437
+ }
4438
+ }
4439
+
4440
+ event.preventDefault();
4441
+ event.stopPropagation();
4442
+ const newEvent = new KeyboardEvent('keydown', {
4443
+ key: event.key,
4444
+ code: event.code,
4445
+ shiftKey: event.shiftKey,
4446
+ altKey: event.altKey,
4447
+ ctrlKey: event.ctrlKey,
4448
+ metaKey: event.metaKey,
4449
+ bubbles: true,
4450
+ composed: true,
4451
+ cancelable: true
4452
+ });
4453
+ this.dispatchEvent(newEvent);
4454
+ });
4455
+ }
4456
+
4457
+ /**
4458
+ * Blocks touch-driven page scroll while a fullscreen modal dialog is open.
4459
+ *
4460
+ * The showModal() function places the dialog in the browser's **top layer**,
4461
+ * which is a separate rendering layer above the normal DOM. On mobile, the
4462
+ * compositor processes visual-viewport panning before top-layer touch
4463
+ * handling. This means the entire viewport — including the top-layer dialog
4464
+ * — can be panned by a touch gesture, causing the page behind the dialog to
4465
+ * scroll into view. To prevent this, we add a touchmove listener that cancels
4466
+ * the event if the touch started outside the dialog or any scrollable child within it.
4467
+ *
4468
+ * @private
4469
+ */
4470
+ _lockTouchScroll() {
4471
+ const dialog = this.shadowRoot.querySelector('dialog');
4472
+
4473
+ this._touchMoveHandler = (event) => {
4474
+ // Walk the composed path (which crosses shadow DOM boundaries) to
4475
+ // check whether the touch started inside a scrollable element that
4476
+ // lives within the dialog. If so, allow the scroll.
4477
+ for (const el of event.composedPath()) {
4478
+ if (el === dialog) {
4479
+ // Reached the dialog boundary without finding a scrollable child.
4480
+ break;
4481
+ }
4482
+ if (el instanceof HTMLElement && el.scrollHeight > el.clientHeight) {
4483
+ const { overflowY } = getComputedStyle(el);
4484
+ if (overflowY === 'auto' || overflowY === 'scroll') {
4485
+ return;
4486
+ }
4487
+ }
4488
+ }
4489
+
4490
+ event.preventDefault();
4491
+ };
4492
+
4493
+ document.addEventListener('touchmove', this._touchMoveHandler, { passive: false });
4494
+ }
4495
+
4496
+ /**
4497
+ * Removes the touchmove listener added by _lockTouchScroll().
4498
+ * @private
4499
+ */
4500
+ _unlockTouchScroll() {
4501
+ if (this._touchMoveHandler) {
4502
+ document.removeEventListener('touchmove', this._touchMoveHandler);
4503
+ this._touchMoveHandler = undefined;
4504
+ }
4505
+ }
4506
+
4507
+ open(modal = true) {
4508
+ const dialog = this.shadowRoot.querySelector('dialog');
4509
+ if (dialog && !dialog.open) {
4510
+ if (modal) {
4511
+ // Prevent showModal() from scrolling the page to bring the dialog
4512
+ // into view. Locking overflow on <html> blocks the viewport scroll
4513
+ // that browsers perform natively; we release it immediately after
4514
+ // so it doesn't interfere with the modal's focus management.
4515
+ const { documentElement } = document;
4516
+ const prevOverflow = documentElement.style.overflow;
4517
+ documentElement.style.overflow = 'hidden';
4518
+
4519
+ dialog.showModal();
4520
+
4521
+ documentElement.style.overflow = prevOverflow;
4522
+
4523
+ this._lockTouchScroll();
4524
+
4525
+ } else {
4526
+ // Use setAttribute instead of dialog.show() to avoid the dialog
4527
+ // focusing steps which steal focus from the trigger and cause
4528
+ // the floater's handleFocusLoss() to immediately hide the bib.
4529
+ dialog.setAttribute('open', '');
4530
+ }
4531
+ }
4532
+ }
4533
+
4534
+ /**
4535
+ * Closes the dialog.
4536
+ */
4537
+ close() {
4538
+ const dialog = this.shadowRoot.querySelector('dialog');
4539
+ if (dialog && dialog.open) {
4540
+ this._unlockTouchScroll();
4541
+ dialog.close();
4542
+ }
4543
+ }
4544
+
4100
4545
  // function that renders the HTML and CSS into the scope of the component
4101
4546
  render() {
4102
4547
  const classes = {
@@ -4108,9 +4553,10 @@ class AuroDropdownBib extends LitElement {
4108
4553
  classes[`shape-${this.shape}`] = true;
4109
4554
 
4110
4555
  return html`
4111
- <div class="${classMap(classes)}" part="bibContainer">
4556
+ <dialog class="${classMap(classes)}" part="bibContainer" role="${ifDefined(this.dialogRole)}" aria-labelledby="${ifDefined(this.dialogLabel ? 'dialogLabel' : undefined)}">
4557
+ ${this.dialogLabel ? html`<span id="dialogLabel" class="util_displayHiddenVisually" aria-hidden="true">${this.dialogLabel}</span>` : ''}
4112
4558
  <slot></slot>
4113
- </div>
4559
+ </dialog>
4114
4560
  `;
4115
4561
  }
4116
4562
  }
@@ -4119,7 +4565,7 @@ var shapeSizeCss = css`.shape-classic-xl,.shape-classic-lg,.shape-classic-md,.sh
4119
4565
 
4120
4566
  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)}`;
4121
4567
 
4122
- var styleCss$1$1 = 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%}`;
4568
+ var styleCss$1$1 = 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%}`;
4123
4569
 
4124
4570
  var classicColorCss = css``;
4125
4571
 
@@ -4357,7 +4803,7 @@ let AuroHelpText$1 = class AuroHelpText extends LitElement {
4357
4803
  }
4358
4804
  };
4359
4805
 
4360
- var formkitVersion$1 = '202602140013';
4806
+ var formkitVersion$1 = '202603102257';
4361
4807
 
4362
4808
  class AuroElement extends LitElement {
4363
4809
  static get properties() {
@@ -4471,7 +4917,7 @@ class AuroElement extends LitElement {
4471
4917
  * The `auro-dropdown` element provides a way to place content in a bib that can be toggled.
4472
4918
  * @customElement auro-dropdown
4473
4919
  *
4474
- * @slot - Default slot for the popover content.
4920
+ * @slot - Default slot for the dropdown bib content.
4475
4921
  * @slot helpText - Defines the content of the helpText.
4476
4922
  * @slot trigger - Defines the content of the trigger.
4477
4923
  * @csspart trigger - The trigger content container.
@@ -4483,6 +4929,13 @@ class AuroElement extends LitElement {
4483
4929
  * @event auroDropdown-idAdded - Notifies consumers that the unique ID for the dropdown bib has been generated.
4484
4930
  */
4485
4931
  class AuroDropdown extends AuroElement {
4932
+ static get shadowRootOptions() {
4933
+ return {
4934
+ ...AuroElement.shadowRootOptions,
4935
+ delegatesFocus: true,
4936
+ };
4937
+ }
4938
+
4486
4939
  constructor() {
4487
4940
  super();
4488
4941
 
@@ -4548,15 +5001,6 @@ class AuroDropdown extends AuroElement {
4548
5001
  this.shift = false;
4549
5002
  this.autoPlacement = false;
4550
5003
 
4551
- /**
4552
- * @private
4553
- * @property {boolean} delegatesFocus - Whether the shadow root delegates focus.
4554
- */
4555
- this.constructor.shadowRootOptions = {
4556
- ...LitElement.shadowRootOptions,
4557
- delegatesFocus: true,
4558
- };
4559
-
4560
5004
  /**
4561
5005
  * @private
4562
5006
  */
@@ -4630,6 +5074,18 @@ class AuroDropdown extends AuroElement {
4630
5074
  */
4631
5075
  show() {
4632
5076
  this.floater.showBib();
5077
+
5078
+ // Open dialog synchronously so callers remain in the user gesture
5079
+ // chain. This is critical for mobile browsers (iOS Safari) to keep
5080
+ // the virtual keyboard open when transitioning from the trigger
5081
+ // input to an input inside the fullscreen dialog. Without this,
5082
+ // showModal() fires asynchronously via Lit's update cycle, which
5083
+ // falls outside the user activation window and causes iOS to
5084
+ // dismiss the keyboard.
5085
+ if (this.isBibFullscreen && this.bibElement && this.bibElement.value) {
5086
+ const useModal = !this.disableFocusTrap;
5087
+ this.bibElement.value.open(useModal);
5088
+ }
4633
5089
  }
4634
5090
 
4635
5091
  /**
@@ -4637,13 +5093,37 @@ class AuroDropdown extends AuroElement {
4637
5093
  * If not, trigger element will get focus.
4638
5094
  */
4639
5095
  focus() {
4640
- if (this.isPopoverVisible && this.focusTrap) {
4641
- this.focusTrap.focusFirstElement();
5096
+ if (this.isPopoverVisible && this.bibContent) {
5097
+ const focusables = getFocusableElements(this.bibContent);
5098
+ if (focusables.length > 0) {
5099
+ focusables[0].focus();
5100
+ }
4642
5101
  } else {
4643
5102
  this.trigger.focus();
4644
5103
  }
4645
5104
  }
4646
5105
 
5106
+ /**
5107
+ * Sets the active descendant element for accessibility.
5108
+ * Uses ariaActiveDescendantElement to cross shadow DOM boundaries.
5109
+ * This function is used in components that contain `auro-dropdown` to set the active descendant.
5110
+ * @private
5111
+ * @param {HTMLElement|null} element - The element to set as the active descendant, or null to clear.
5112
+ * @returns {void}
5113
+ */
5114
+ setActiveDescendant(element) {
5115
+ if (!this.trigger) {
5116
+ return;
5117
+ }
5118
+
5119
+ if (element) {
5120
+ this.trigger.ariaActiveDescendantElement = element;
5121
+ } else {
5122
+ this.trigger.ariaActiveDescendantElement = null;
5123
+ this.trigger.removeAttribute('aria-activedescendant');
5124
+ }
5125
+ }
5126
+
4647
5127
  // function to define props used within the scope of this component
4648
5128
  static get properties() {
4649
5129
  return {
@@ -4901,6 +5381,16 @@ class AuroDropdown extends AuroElement {
4901
5381
  */
4902
5382
  tabIndex: {
4903
5383
  type: Number
5384
+ },
5385
+
5386
+ /**
5387
+ * Accessible label for the dropdown bib dialog element.
5388
+ * @private
5389
+ */
5390
+ bibDialogLabel: {
5391
+ type: String,
5392
+ attribute: false,
5393
+ reflect: false
4904
5394
  }
4905
5395
  };
4906
5396
  }
@@ -4952,7 +5442,10 @@ class AuroDropdown extends AuroElement {
4952
5442
 
4953
5443
  disconnectedCallback() {
4954
5444
  super.disconnectedCallback();
4955
- this.floater.disconnect();
5445
+ if (this.floater) {
5446
+ this.floater.hideBib('disconnect');
5447
+ this.floater.disconnect();
5448
+ }
4956
5449
  this.clearTriggerFocusEventBinding();
4957
5450
  }
4958
5451
 
@@ -4974,11 +5467,22 @@ class AuroDropdown extends AuroElement {
4974
5467
 
4975
5468
  if (changedProperties.has('isPopoverVisible') && this.bibElement.value) {
4976
5469
  if (this.isPopoverVisible) {
4977
- this.bibElement.value.showPopover();
5470
+ // Fullscreen: use showModal() for native accessibility (inert outside, focus trap)
5471
+ // Desktop: use show() for Floating UI positioning + FocusTrap for focus management
5472
+ const useModal = this.isBibFullscreen && !this.disableFocusTrap;
5473
+ this.bibElement.value.open(useModal);
4978
5474
  } else {
4979
- this.bibElement.value.hidePopover();
5475
+ this.bibElement.value.close();
4980
5476
  }
4981
5477
  }
5478
+
5479
+ // When fullscreen strategy changes while open, re-open dialog with correct mode
5480
+ // (e.g. resizing from desktop → mobile while dropdown is open)
5481
+ if (changedProperties.has('isBibFullscreen') && this.isPopoverVisible && this.bibElement.value) {
5482
+ const useModal = this.isBibFullscreen && !this.disableFocusTrap;
5483
+ this.bibElement.value.close();
5484
+ this.bibElement.value.open(useModal);
5485
+ }
4982
5486
  }
4983
5487
 
4984
5488
  /**
@@ -4996,11 +5500,28 @@ class AuroDropdown extends AuroElement {
4996
5500
  }
4997
5501
 
4998
5502
  firstUpdated() {
4999
-
5000
5503
  // Configure the floater to, this will generate the ID for the bib
5001
5504
  this.floater.configure(this, 'auroDropdown');
5505
+
5506
+ // Prevent `contain: layout` on the dropdown host. Layout containment
5507
+ // creates a containing block for position:fixed descendants (the bib),
5508
+ // which clips the bib inside ancestor overflow contexts such as a
5509
+ // <dialog> element. Without it, the bib's position:fixed is relative
5510
+ // to the viewport, letting Floating UI's flip middleware detect
5511
+ // viewport boundaries and the bib escape overflow clipping.
5512
+ const origConfigureBibStrategy = this.floater.configureBibStrategy.bind(this.floater);
5513
+ this.floater.configureBibStrategy = (value) => {
5514
+ origConfigureBibStrategy(value);
5515
+ this.style.contain = '';
5516
+ };
5517
+
5002
5518
  this.addEventListener('auroDropdown-toggled', this.handleDropdownToggle);
5003
5519
 
5520
+ // Handle ESC key from dialog's cancel event
5521
+ this.addEventListener('auro-bib-cancel', () => {
5522
+ this.floater.hideBib('keydown');
5523
+ });
5524
+
5004
5525
  /**
5005
5526
  * @description Let subscribers know that the dropdown ID ha been generated and added.
5006
5527
  * @event auroDropdown-idAdded
@@ -5008,9 +5529,9 @@ class AuroDropdown extends AuroElement {
5008
5529
  */
5009
5530
  this.dispatchEvent(new CustomEvent('auroDropdown-idAdded', {detail: {id: this.floater.element.id}}));
5010
5531
 
5011
- // Set the bib ID locally if the user hasn't provided a focusable trigger
5532
+ // Set the bib ID locally for aria-controls (must be in the same shadow root as the trigger)
5012
5533
  if (!this.triggerContentFocusable) {
5013
- this.dropdownId = this.floater.element.id;
5534
+ this.dropdownId = this.floater.element.bib.id;
5014
5535
  }
5015
5536
 
5016
5537
  this.bibContent = this.floater.element.bib;
@@ -5070,21 +5591,20 @@ class AuroDropdown extends AuroElement {
5070
5591
  * @private
5071
5592
  */
5072
5593
  updateFocusTrap() {
5073
- // If the dropdown is open, create a focus trap and focus the first element
5074
5594
  if (this.isPopoverVisible && !this.disableFocusTrap) {
5075
- this.focusTrap = new FocusTrap(this.bibContent);
5076
- this.focusTrap.focusFirstElement();
5595
+ if (!this.isBibFullscreen) {
5596
+ // Desktop: show() doesn't trap focus, so use FocusTrap
5597
+ this.focusTrap = new FocusTrap(this.bibContent);
5598
+ this.focusTrap.focusFirstElement();
5599
+ }
5600
+ // Fullscreen: showModal() provides native focus trapping
5077
5601
  return;
5078
5602
  }
5079
5603
 
5080
- // Guard Clause: Ensure there is a focus trap currently active before continuing
5081
- if (!this.focusTrap) {
5082
- return;
5604
+ if (this.focusTrap) {
5605
+ this.focusTrap.disconnect();
5606
+ this.focusTrap = undefined;
5083
5607
  }
5084
-
5085
- // If the dropdown is not open, disconnect the focus trap if it exists
5086
- this.focusTrap.disconnect();
5087
- this.focusTrap = undefined;
5088
5608
  }
5089
5609
 
5090
5610
  /**
@@ -5300,13 +5820,14 @@ class AuroDropdown extends AuroElement {
5300
5820
  <div
5301
5821
  id="showStateIcon"
5302
5822
  class="chevron"
5303
- part="chevron">
5823
+ part="chevron"
5824
+ aria-hidden="true">
5304
5825
  <${this.iconTag}
5305
5826
  category="interface"
5306
5827
  name="${this.isPopoverVisible ? 'chevron-up' : `chevron-down`}"
5307
5828
  appearance="${this.onDark ? 'inverse' : this.appearance}"
5308
- variant="${this.disabled ? 'disabled' : 'muted'}">
5309
- >
5829
+ variant="${this.disabled ? 'disabled' : 'muted'}"
5830
+ ariaHidden="true">
5310
5831
  </${this.iconTag}>
5311
5832
  </div>
5312
5833
  ` : undefined }
@@ -5320,8 +5841,8 @@ class AuroDropdown extends AuroElement {
5320
5841
  shape="${this.shape}"
5321
5842
  ?data-show="${this.isPopoverVisible}"
5322
5843
  ?isfullscreen="${this.isBibFullscreen}"
5844
+ .dialogLabel="${this.bibDialogLabel}"
5323
5845
  ${ref(this.bibElement)}
5324
- popover="manual"
5325
5846
  >
5326
5847
  <div class="slotContent">
5327
5848
  <slot @slotchange="${this.handleDefaultSlot}"></slot>
@@ -5728,6 +6249,18 @@ class AuroBibtemplate extends LitElement {
5728
6249
  this.removeEventListener('touchmove', this.preventBodyScroll, { passive: false });
5729
6250
  }
5730
6251
 
6252
+ /**
6253
+ * Focuses the close button inside the bibtemplate's shadow DOM.
6254
+ * Used by parent components to set initial focus when the fullscreen dialog opens.
6255
+ * @returns {void}
6256
+ */
6257
+ focusCloseButton() {
6258
+ const closeBtn = this.shadowRoot.querySelector('#closeButton');
6259
+ if (closeBtn) {
6260
+ closeBtn.focus();
6261
+ }
6262
+ }
6263
+
5731
6264
  onCloseButtonClick() {
5732
6265
  this.dispatchEvent(new Event("close-click", { bubbles: true,
5733
6266
  composed: true }));
@@ -6014,7 +6547,7 @@ class AuroHelpText extends LitElement {
6014
6547
  }
6015
6548
  }
6016
6549
 
6017
- var formkitVersion = '202602140013';
6550
+ var formkitVersion = '202603102257';
6018
6551
 
6019
6552
  var styleCss = 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}.body-default{font-size:var(--wcss-body-default-font-size, 1rem);line-height:var(--wcss-body-default-line-height, 1.5rem)}.body-default,.body-lg{font-family:var(--wcss-body-family, "AS Circular"),system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;font-weight:var(--wcss-body-weight, 450);letter-spacing:var(--wcss-body-letter-spacing, 0)}.body-lg{font-size:var(--wcss-body-lg-font-size, 1.125rem);line-height:var(--wcss-body-lg-line-height, 1.625rem)}.body-sm{font-size:var(--wcss-body-sm-font-size, 0.875rem);line-height:var(--wcss-body-sm-line-height, 1.25rem)}.body-sm,.body-xs{font-family:var(--wcss-body-family, "AS Circular"),system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;font-weight:var(--wcss-body-weight, 450);letter-spacing:var(--wcss-body-letter-spacing, 0)}.body-xs{font-size:var(--wcss-body-xs-font-size, 0.75rem);line-height:var(--wcss-body-xs-line-height, 1rem)}.body-2xs{font-family:var(--wcss-body-family, "AS Circular"),system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;font-size:var(--wcss-body-2xs-font-size, 0.625rem);font-weight:var(--wcss-body-weight, 450);letter-spacing:var(--wcss-body-letter-spacing, 0);line-height:var(--wcss-body-2xs-line-height, 0.875rem)}.display-2xl{font-family:var(--wcss-display-2xl-family, "AS Circular"),var(--wcss-display-2xl-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);font-size:var(--wcss-display-2xl-font-size, clamp(3.5rem, 6vw, 5.375rem));font-weight:var(--wcss-display-2xl-weight, 300);letter-spacing:var(--wcss-display-2xl-letter-spacing, 0);line-height:var(--wcss-display-2xl-line-height, 1.3)}.display-xl{font-family:var(--wcss-display-xl-family, "AS Circular"),var(--wcss-display-xl-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);font-size:var(--wcss-display-xl-font-size, clamp(3rem, 5.3333333333vw, 4.5rem));font-weight:var(--wcss-display-xl-weight, 300);letter-spacing:var(--wcss-display-xl-letter-spacing, 0);line-height:var(--wcss-display-xl-line-height, 1.3)}.display-lg{font-family:var(--wcss-display-lg-family, "AS Circular"),var(--wcss-display-lg-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);font-size:var(--wcss-display-lg-font-size, clamp(2.75rem, 4.6666666667vw, 4rem));font-weight:var(--wcss-display-lg-weight, 300);letter-spacing:var(--wcss-display-lg-letter-spacing, 0);line-height:var(--wcss-display-lg-line-height, 1.3)}.display-md{font-family:var(--wcss-display-md-family, "AS Circular"),var(--wcss-display-md-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);font-size:var(--wcss-display-md-font-size, clamp(2.5rem, 4vw, 3.5rem));font-weight:var(--wcss-display-md-weight, 300);letter-spacing:var(--wcss-display-md-letter-spacing, 0);line-height:var(--wcss-display-md-line-height, 1.3)}.display-sm{font-family:var(--wcss-display-sm-family, "AS Circular"),var(--wcss-display-sm-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);font-size:var(--wcss-display-sm-font-size, clamp(2rem, 3.6666666667vw, 3rem));font-weight:var(--wcss-display-sm-weight, 300);letter-spacing:var(--wcss-display-sm-letter-spacing, 0);line-height:var(--wcss-display-sm-line-height, 1.3)}.display-xs{font-family:var(--wcss-display-xs-family, "AS Circular"),var(--wcss-display-xs-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);font-size:var(--wcss-display-xs-font-size, clamp(1.75rem, 3vw, 2.375rem));font-weight:var(--wcss-display-xs-weight, 300);letter-spacing:var(--wcss-display-xs-letter-spacing, 0);line-height:var(--wcss-display-xs-line-height, 1.3)}.heading-xl{font-family:var(--wcss-heading-xl-family, "AS Circular"),var(--wcss-heading-xl-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);font-size:var(--wcss-heading-xl-font-size, clamp(2rem, 3vw, 2.5rem));font-weight:var(--wcss-heading-xl-weight, 300);letter-spacing:var(--wcss-heading-xl-letter-spacing, 0);line-height:var(--wcss-heading-xl-line-height, 1.3)}.heading-lg{font-family:var(--wcss-heading-lg-family, "AS Circular"),var(--wcss-heading-lg-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);font-size:var(--wcss-heading-lg-font-size, clamp(1.75rem, 2.6666666667vw, 2.25rem));font-weight:var(--wcss-heading-lg-weight, 300);letter-spacing:var(--wcss-heading-lg-letter-spacing, 0);line-height:var(--wcss-heading-lg-line-height, 1.3)}.heading-md{font-family:var(--wcss-heading-md-family, "AS Circular"),var(--wcss-heading-md-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);font-size:var(--wcss-heading-md-font-size, clamp(1.625rem, 2.3333333333vw, 1.75rem));font-weight:var(--wcss-heading-md-weight, 300);letter-spacing:var(--wcss-heading-md-letter-spacing, 0);line-height:var(--wcss-heading-md-line-height, 1.3)}.heading-sm{font-family:var(--wcss-heading-sm-family, "AS Circular"),var(--wcss-heading-sm-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);font-size:var(--wcss-heading-sm-font-size, clamp(1.375rem, 2vw, 1.5rem));font-weight:var(--wcss-heading-sm-weight, 300);letter-spacing:var(--wcss-heading-sm-letter-spacing, 0);line-height:var(--wcss-heading-sm-line-height, 1.3)}.heading-xs{font-family:var(--wcss-heading-xs-family, "AS Circular"),var(--wcss-heading-xs-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);font-size:var(--wcss-heading-xs-font-size, clamp(1.25rem, 1.6666666667vw, 1.25rem));font-weight:var(--wcss-heading-xs-weight, 450);letter-spacing:var(--wcss-heading-xs-letter-spacing, 0);line-height:var(--wcss-heading-xs-line-height, 1.3)}.heading-2xs{font-family:var(--wcss-heading-2xs-family, "AS Circular"),var(--wcss-heading-2xs-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);font-size:var(--wcss-heading-2xs-font-size, clamp(1.125rem, 1.5vw, 1.125rem));font-weight:var(--wcss-heading-2xs-weight, 450);letter-spacing:var(--wcss-heading-2xs-letter-spacing, 0);line-height:var(--wcss-heading-2xs-line-height, 1.3)}.accent-2xl{font-family:var(--wcss-accent-2xl-family, "Good OT"),var(--wcss-accent-2xl-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);font-size:var(--wcss-accent-2xl-font-size, clamp(2rem, 3.1666666667vw, 2.375rem));font-weight:var(--wcss-accent-2xl-weight, 450);letter-spacing:var(--wcss-accent-2xl-letter-spacing, 0.05em);line-height:var(--wcss-accent-2xl-line-height, 1)}.accent-2xl,.accent-xl{text-transform:uppercase}.accent-xl{font-family:var(--wcss-accent-xl-family, "Good OT"),var(--wcss-accent-xl-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);font-size:var(--wcss-accent-xl-font-size, clamp(1.625rem, 2.3333333333vw, 2rem));font-weight:var(--wcss-accent-xl-weight, 450);letter-spacing:var(--wcss-accent-xl-letter-spacing, 0.05em);line-height:var(--wcss-accent-xl-line-height, 1.3)}.accent-lg{font-family:var(--wcss-accent-lg-family, "Good OT"),var(--wcss-accent-lg-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);font-size:var(--wcss-accent-lg-font-size, clamp(1.5rem, 2.1666666667vw, 1.75rem));font-weight:var(--wcss-accent-lg-weight, 450);letter-spacing:var(--wcss-accent-lg-letter-spacing, 0.05em);line-height:var(--wcss-accent-lg-line-height, 1.3)}.accent-lg,.accent-md{text-transform:uppercase}.accent-md{font-family:var(--wcss-accent-md-family, "Good OT"),var(--wcss-accent-md-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);font-size:var(--wcss-accent-md-font-size, clamp(1.375rem, 1.8333333333vw, 1.5rem));font-weight:var(--wcss-accent-md-weight, 500);letter-spacing:var(--wcss-accent-md-letter-spacing, 0.05em);line-height:var(--wcss-accent-md-line-height, 1.3)}.accent-sm{font-family:var(--wcss-accent-sm-family, "Good OT"),var(--wcss-accent-sm-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);font-size:var(--wcss-accent-sm-font-size, clamp(1.125rem, 1.5vw, 1.25rem));font-weight:var(--wcss-accent-sm-weight, 500);letter-spacing:var(--wcss-accent-sm-letter-spacing, 0.05em);line-height:var(--wcss-accent-sm-line-height, 1.3)}.accent-sm,.accent-xs{text-transform:uppercase}.accent-xs{font-family:var(--wcss-accent-xs-family, "Good OT"),var(--wcss-accent-xs-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);font-size:var(--wcss-accent-xs-font-size, clamp(1rem, 1.3333333333vw, 1rem));font-weight:var(--wcss-accent-xs-weight, 500);letter-spacing:var(--wcss-accent-xs-letter-spacing, 0.1em);line-height:var(--wcss-accent-xs-line-height, 1.3)}.accent-2xs{font-family:var(--wcss-accent-2xs-family, "Good OT"),var(--wcss-accent-2xs-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);font-size:var(--wcss-accent-2xs-font-size, clamp(0.875rem, 1.1666666667vw, 0.875rem));font-weight:var(--wcss-accent-2xs-weight, 450);letter-spacing:var(--wcss-accent-2xs-letter-spacing, 0.1em);line-height:var(--wcss-accent-2xs-line-height, 1.3);text-transform:uppercase}[auro-dropdown]{--ds-auro-dropdown-trigger-border-color: var(--ds-auro-select-border-color);--ds-auro-dropdown-trigger-background-color: var(--ds-auro-select-background-color);--ds-auro-dropdown-trigger-container-color: var(--ds-auro-select-background-color);--ds-auro-dropdown-trigger-outline-color: var(--ds-auro-select-outline-color)}:host{display:inline-block;text-align:left;vertical-align:top}:host([layout*=emphasized]) [auro-dropdown],:host([layout*=snowflake]) [auro-dropdown]{--ds-auro-select-border-color: transparent}:host([layout*=emphasized]) .mainContent,:host([layout*=snowflake]) .mainContent{text-align:center}.mainContent{position:relative;display:flex;overflow:hidden;flex:1;flex-direction:column;align-items:center;justify-content:center}.valueContainer [slot=displayValue]{display:none}.accents{display:flex;flex-direction:row;align-items:center;justify-content:center}::slotted([slot=typeIcon]){margin-right:var(--ds-size-100, 0.5rem)}.displayValue{display:block}.displayValue:not(.force){display:none}.displayValue:not(.force).hasContent:is(.withValue):not(.hasFocus){display:block}.triggerContent{display:flex;width:100%;align-items:center;justify-content:center}:host([layout*=emphasized]) .triggerContent{padding:0 var(--ds-size-100, 0.5rem) 0 var(--ds-size-300, 1.5rem)}:host([layout*=snowflake]) .triggerContent{padding:0 var(--ds-size-100, 0.5rem) 0 var(--ds-size-200, 1rem)}:host([layout*=snowflake]) label{padding-block:var(--ds-size-25, 0.125rem)}:host([layout*=classic]) .triggerContent{padding:0 var(--ds-size-100, 0.5rem)}:host([layout*=classic]) .mainContent{align-items:start}:host([layout*=classic]) label{overflow:hidden;cursor:text;text-overflow:ellipsis;white-space:nowrap}:host([layout*=classic]) .value{height:auto}label{color:var(--ds-auro-select-label-text-color)}:host(:is([validity]:not([validity=valid]))) [auro-dropdown]{--ds-auro-select-border-color: var(--ds-basic-color-status-error, #e31f26);--ds-auro-select-outline-color: var(--ds-basic-color-status-error, #e31f26);--ds-auro-dropdown-helptext-text-color: var(--ds-basic-color-texticon-default, #2a2a2a)}:host([ondark]:is([validity]:not([validity=valid]))) [auro-dropdown],:host([appearance=inverse]:is([validity]:not([validity=valid]))) [auro-dropdown]{--ds-auro-select-border-color: var(--ds-advanced-color-state-error-inverse, #f9a4a8);--ds-auro-select-outline-color: var(--ds-advanced-color-state-error-inverse, #f9a4a8);--ds-auro-dropdown-helptext-text-color: var(--ds-basic-color-texticon-inverse, #ffffff)}#slotHolder{display:none}:host([fluid]){width:100%}:host([disabled]){pointer-events:none;user-select:none}:host([disabled]:not([ondark])) [auro-dropdown],:host([disabled]:not([appearance=inverse])) [auro-dropdown]{--ds-auro-select-border-color: var(--ds-basic-color-border-subtle, #dddddd)}:host(:not([layout*=classic])[disabled][ondark]) [auro-dropdown],:host(:not([layout*=classic])[disabled][appearance=inverse]) [auro-dropdown]{--ds-auro-select-border-color: transparent}`;
6020
6553
 
@@ -6534,28 +7067,76 @@ class AuroSelect extends AuroElement$1 {
6534
7067
  configureDropdown() {
6535
7068
  this.dropdown = this.shadowRoot.querySelector(this.dropdownTag._$litStatic$);
6536
7069
 
7070
+ // Prevent dropdown from closing on focus loss since menu content is slotted
7071
+ // from select's light DOM and won't be detected by dropdown's contains() check.
7072
+ // Select handles Tab key closing explicitly, ESC via dialog cancel, and
7073
+ // click outside works correctly via composedPath().
7074
+ this.dropdown.noHideOnThisFocusLoss = true;
7075
+
7076
+
6537
7077
  this.dropdown.addEventListener('auroDropdown-toggled', () => {
6538
7078
  this.isPopoverVisible = this.dropdown.isPopoverVisible;
6539
7079
 
7080
+ // Clear aria-activedescendant when dropdown closes
7081
+ if (!this.dropdown.isPopoverVisible) {
7082
+ this.dropdown.setActiveDescendant(null);
7083
+ this.optionActive = null;
7084
+
7085
+ restoreTriggerAfterClose(this.dropdown, this.dropdown.trigger);
7086
+ }
7087
+
6540
7088
  if (this.dropdown.isPopoverVisible) {
6541
7089
  this.updateMenuShapeSize();
6542
- // wait til the bib gets fully rendered
6543
- setTimeout(() => {
6544
- if (this.dropdown.isBibFullscreen) {
6545
- // trigger holds the focus since menu is not a focusable element.
6546
- this.dropdown.trigger.focus();
6547
7090
 
6548
- // default focus indicator on the first menu option
6549
- if (this.menu.index < 0) {
6550
- this.menu.navigateOptions('down');
7091
+ if (this.dropdown.isBibFullscreen) {
7092
+ // Hide the trigger from assistive technology so VoiceOver cannot reach it
7093
+ // behind the fullscreen dialog
7094
+ this.dropdown.trigger.inert = true;
7095
+
7096
+ guardTouchPassthrough(this.menu);
7097
+
7098
+ // Wait for the bibtemplate to fully render (close button) across
7099
+ // multiple Lit update cycles before moving focus into the bib
7100
+ doubleRaf(() => {
7101
+ this.bibtemplate.focusCloseButton();
7102
+
7103
+ // If there's a selected option, highlight it (per W3C APG combobox-select-only pattern)
7104
+ // No selection → no highlight
7105
+ if (this.optionSelected && !Array.isArray(this.optionSelected)) {
7106
+ this.menu.updateActiveOption(this.optionSelected);
7107
+ } else if (this.multiSelect && Array.isArray(this.optionSelected) && this.optionSelected.length > 0) {
7108
+ this.menu.updateActiveOption(this.optionSelected[0]);
6551
7109
  }
6552
- }
6553
- });
7110
+
7111
+ // Scroll the selected option into view when dropdown opens
7112
+ this.scrollSelectedOptionIntoView();
7113
+ });
7114
+ } else {
7115
+ // wait til the bib gets fully rendered
7116
+ setTimeout(() => {
7117
+ // Keep focus on trigger so aria-activedescendant announces menu options
7118
+ this.dropdown.trigger.focus();
7119
+
7120
+ // Scroll the selected option into view when dropdown opens
7121
+ this.scrollSelectedOptionIntoView();
7122
+ });
7123
+ }
6554
7124
  }
6555
7125
  });
6556
7126
 
6557
7127
  this.dropdown.addEventListener('auroDropdown-strategy-change', () => {
6558
7128
  this.updateMenuShapeSize();
7129
+
7130
+ // When switching to fullscreen while open, focus the close button
7131
+ // so it's not stuck on the trigger behind the dialog
7132
+ if (this.dropdown.isBibFullscreen && this.dropdown.isPopoverVisible) {
7133
+ this.dropdown.trigger.inert = true;
7134
+ doubleRaf(() => {
7135
+ this.bibtemplate.focusCloseButton();
7136
+ });
7137
+ } else if (!this.dropdown.isBibFullscreen) {
7138
+ this.dropdown.trigger.inert = false;
7139
+ }
6559
7140
  });
6560
7141
 
6561
7142
  // setting up bibtemplate
@@ -6565,6 +7146,12 @@ class AuroSelect extends AuroElement$1 {
6565
7146
  this.dropdown.dropdownWidth = this.customBibWidth;
6566
7147
  }
6567
7148
 
7149
+ // Pass label text to the dropdown bib for accessible dialog naming
7150
+ const labelElement = this.querySelector('span[slot="label"]');
7151
+ if (labelElement) {
7152
+ this.dropdown.bibDialogLabel = labelElement.textContent.trim() || undefined;
7153
+ }
7154
+
6568
7155
  // Exposes the CSS parts from the dropdown for styling
6569
7156
  this.dropdown.exposeCssParts();
6570
7157
  }
@@ -6666,6 +7253,23 @@ class AuroSelect extends AuroElement$1 {
6666
7253
  }
6667
7254
  }
6668
7255
 
7256
+ /**
7257
+ * Sets aria-setsize and aria-posinset on each menu option so screen readers
7258
+ * can announce position even when the listbox context is broken by
7259
+ * shadow DOM boundaries.
7260
+ * @private
7261
+ * @returns {void}
7262
+ */
7263
+ updateOptionPositions() {
7264
+ if (!this.menu || !this.menu.options) return;
7265
+ const visibleOptions = this.menu.options;
7266
+ const count = visibleOptions.length;
7267
+ visibleOptions.forEach((option, index) => {
7268
+ option.setAttribute('aria-setsize', count);
7269
+ option.setAttribute('aria-posinset', index + 1);
7270
+ });
7271
+ }
7272
+
6669
7273
  /**
6670
7274
  * Binds all behavior needed to the menu after rendering.
6671
7275
  * @private
@@ -6687,13 +7291,38 @@ class AuroSelect extends AuroElement$1 {
6687
7291
  this.updateMenuShapeSize();
6688
7292
  this.setMenuValue(this.value);
6689
7293
 
7294
+ // Set accessible name on the menu for screen readers based on the label slot content
7295
+ const labelElement = this.querySelector('[slot="label"]');
7296
+ if (labelElement) {
7297
+ this.menu.setAttribute('aria-label', labelElement.textContent.trim());
7298
+ }
7299
+
6690
7300
  if (this.multiSelect) {
6691
7301
  this.menu.multiSelect = this.multiSelect;
6692
7302
  }
6693
7303
 
6694
7304
  this.options = this.menu.options;
6695
- this.menu.setAttribute('aria-hidden', 'true');
7305
+ this.updateOptionPositions();
6696
7306
  this.menu.addEventListener("auroMenu-loadingChange", (event) => this.handleMenuLoadingChange(event));
7307
+
7308
+ this.menu.addEventListener('auroMenu-activatedOption', (evt) => {
7309
+ this.optionActive = evt.detail;
7310
+
7311
+ if (this.dropdown) {
7312
+ this.dropdown.setActiveDescendant(this.optionActive);
7313
+ }
7314
+
7315
+ // Announce the active option for screen readers
7316
+ if (this.optionActive) {
7317
+ const optionText = this.optionActive.textContent.trim();
7318
+ const selectedState = this.optionActive.hasAttribute('selected') ? ', selected' : ', not selected';
7319
+ announceToScreenReader(this.shadowRoot, `${optionText}${selectedState}`);
7320
+ }
7321
+
7322
+ if (this.dropdown.isPopoverVisible) {
7323
+ this.scrollActiveOptionIntoView();
7324
+ }
7325
+ });
6697
7326
  this.menu.addEventListener('auroMenu-selectedOption', (event) => {
6698
7327
 
6699
7328
  // Update the displayed value
@@ -6706,6 +7335,14 @@ class AuroSelect extends AuroElement$1 {
6706
7335
  if (this.dropdown.isPopoverVisible) {
6707
7336
  this.dropdown.hide();
6708
7337
  }
7338
+
7339
+ // Announce the selection after the dropdown closes so it isn't
7340
+ // overridden by VoiceOver's "collapsed" announcement from aria-expanded.
7341
+ const selectedValue = event.detail.stringValue;
7342
+ const announcementDelay = 300;
7343
+ setTimeout(() => {
7344
+ announceToScreenReader(this.shadowRoot, `${selectedValue}, selected`);
7345
+ }, announcementDelay);
6709
7346
  });
6710
7347
  }
6711
7348
 
@@ -6717,66 +7354,7 @@ class AuroSelect extends AuroElement$1 {
6717
7354
  configureSelect() {
6718
7355
  this.nativeSelect = this.shadowRoot.querySelector('select');
6719
7356
 
6720
- this.addEventListener('keydown', (evt) => {
6721
-
6722
- // when the focus is on trigger not on close button
6723
- if (this.dropdown.shadowRoot.activeElement === this.dropdown.trigger) {
6724
- if (evt.key === 'ArrowUp') {
6725
- evt.preventDefault();
6726
-
6727
- this.dropdown.show();
6728
-
6729
- if (this.dropdown.isPopoverVisible) {
6730
- this.menu.navigateOptions('up');
6731
- }
6732
-
6733
- return;
6734
- }
6735
-
6736
- if (evt.key === 'ArrowDown') {
6737
- evt.preventDefault();
6738
-
6739
- this.dropdown.show();
6740
-
6741
- if (this.dropdown.isPopoverVisible) {
6742
- this.menu.navigateOptions('down');
6743
- }
6744
-
6745
- return;
6746
- }
6747
-
6748
- if (evt.key === 'Enter') {
6749
- if (!this.dropdown.isPopoverVisible) {
6750
- evt.preventDefault();
6751
- this.menu.makeSelection();
6752
- }
6753
-
6754
- return;
6755
- }
6756
- }
6757
-
6758
- if (evt.key === 'Tab' && this.dropdown.isPopoverVisible) {
6759
- if (this.dropdown.isBibFullscreen) {
6760
- evt.preventDefault();
6761
-
6762
- // when the focus is on trigger not on close button
6763
- if (this.dropdown.shadowRoot.activeElement === this.dropdown.trigger) {
6764
- // `dropdown.focus` will move focus to the first focusable element in bib when it's open,
6765
- // when bib it not open, it will focus onto trigger.
6766
- this.dropdown.focus();
6767
- } else {
6768
- // when close button has the focus, move focus back to the trigger
6769
- this.dropdown.trigger.focus();
6770
- }
6771
- } else {
6772
- this.dropdown.hide();
6773
- }
6774
- return;
6775
- }
6776
-
6777
- // Handle all other key presses by updating the active option based on the key pressed
6778
- this.updateActiveOptionBasedOnKey(evt.key);
6779
- });
7357
+ applyKeyboardStrategy(this, selectKeyboardStrategy);
6780
7358
 
6781
7359
  this.addEventListener('focusin', this.handleFocusin);
6782
7360
 
@@ -6786,6 +7364,16 @@ class AuroSelect extends AuroElement$1 {
6786
7364
  });
6787
7365
  }
6788
7366
 
7367
+ /**
7368
+ * Updates the active option in the menu.
7369
+ * @param {number} index - Index of the option to make active.
7370
+ */
7371
+ updateActiveOption(index) {
7372
+ if (this.menu) {
7373
+ this.menu.index = index;
7374
+ }
7375
+ }
7376
+
6789
7377
  /**
6790
7378
  * Updates the active option in the menu based on keyboard input.
6791
7379
  * @private
@@ -6949,6 +7537,48 @@ class AuroSelect extends AuroElement$1 {
6949
7537
  this.menu.value = value;
6950
7538
  }
6951
7539
 
7540
+ /**
7541
+ * Scrolls the currently active option into view.
7542
+ * Respects user's motion preferences for accessibility.
7543
+ * @private
7544
+ */
7545
+ scrollActiveOptionIntoView() {
7546
+ if (!this.menu || !this.menu.optionActive) return;
7547
+
7548
+ // Check if user prefers reduced motion for accessibility
7549
+ const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
7550
+
7551
+ this.menu.optionActive.scrollIntoView({
7552
+ alignToTop: false,
7553
+ block: "nearest",
7554
+ behavior: prefersReducedMotion ? "auto" : "smooth"
7555
+ });
7556
+ }
7557
+
7558
+ /**
7559
+ * Scrolls the currently selected option into view.
7560
+ * Respects user's motion preferences for accessibility.
7561
+ * @private
7562
+ */
7563
+ scrollSelectedOptionIntoView() {
7564
+ if (!this.menu || !this.menu.optionSelected) return;
7565
+
7566
+ const selectedOption = this.multiSelect && Array.isArray(this.menu.optionSelected)
7567
+ ? this.menu.optionSelected[0]
7568
+ : this.menu.optionSelected;
7569
+
7570
+ if (selectedOption) {
7571
+ // Check if user prefers reduced motion for accessibility
7572
+ const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
7573
+
7574
+ selectedOption.scrollIntoView({
7575
+ alignToTop: true,
7576
+ block: "start",
7577
+ behavior: prefersReducedMotion ? "auto" : "smooth"
7578
+ });
7579
+ }
7580
+ }
7581
+
6952
7582
  updated(changedProperties) {
6953
7583
  if (changedProperties.has('multiSelect') && !changedProperties.has('value')) {
6954
7584
  this.clearSelection();
@@ -6960,7 +7590,9 @@ class AuroSelect extends AuroElement$1 {
6960
7590
  this._updateNativeSelect();
6961
7591
  this.validate();
6962
7592
  this.hideBib();
6963
- this.focus();
7593
+ if (this.dropdown && this.dropdown.trigger) {
7594
+ this.dropdown.trigger.focus();
7595
+ }
6964
7596
 
6965
7597
  // LEGACY EVENT
6966
7598
  this.dispatchEvent(new CustomEvent('auroSelect-valueSet', {
@@ -7009,6 +7641,7 @@ class AuroSelect extends AuroElement$1 {
7009
7641
  * @returns {void}
7010
7642
  */
7011
7643
  reset() {
7644
+ this.menu.reset();
7012
7645
  this.validation.reset(this);
7013
7646
  }
7014
7647
 
@@ -7158,7 +7791,7 @@ class AuroSelect extends AuroElement$1 {
7158
7791
  </div>
7159
7792
  <${this.dropdownTag}
7160
7793
  appearance="${this.onDark ? 'inverse' : this.appearance}"
7161
- a11yRole="select"
7794
+ .a11yRole=${"combobox"}
7162
7795
  ?autoPlacement="${this.autoPlacement}"
7163
7796
  ?error="${this.validity !== undefined && this.validity !== 'valid'}"
7164
7797
  ?matchWidth="${this.matchWidth}"
@@ -7173,16 +7806,16 @@ class AuroSelect extends AuroElement$1 {
7173
7806
  part="dropdown"
7174
7807
  shape="${this.shape}"
7175
7808
  size="${this.size}">
7176
- <div slot="trigger" aria-haspopup="true" id="triggerFocus" class="triggerContent">
7809
+ <div slot="trigger" id="triggerFocus" class="triggerContent">
7177
7810
  <div class="accents left">
7178
7811
  <slot name="typeIcon"></slot>
7179
7812
  </div>
7180
7813
  <div class="mainContent">
7181
7814
  <div class="${classMap(valueContainerClasses)}">
7182
- <label class="${classMap(this.commonLabelClasses)}">
7815
+ <span id="dropdownLabel" class="${classMap(this.commonLabelClasses)}">
7183
7816
  <slot name="label"></slot>
7184
7817
  ${this.required ? undefined : html`<slot name="optionalLabel"> (optional)</slot>`}
7185
- </label>
7818
+ </span>
7186
7819
  <div class="value" id="value"></div>
7187
7820
  <div id="placeholder" class="${classMap(placeholderClass)}">
7188
7821
  ${this.placeholder}
@@ -7194,7 +7827,6 @@ class AuroSelect extends AuroElement$1 {
7194
7827
  </div>
7195
7828
  <div class="accents right"></div>
7196
7829
  </div>
7197
- <div class="menuWrapper"></div>
7198
7830
  <${this.bibtemplateTag} ?large="${this.largeFullscreenHeadline}" @close-click="${this.hideBib}">
7199
7831
  <slot name="ariaLabel.bib.close" slot="ariaLabel.close">Close</slot>
7200
7832
  <slot></slot>
@@ -7238,6 +7870,7 @@ class AuroSelect extends AuroElement$1 {
7238
7870
  </div>
7239
7871
  <${this.dropdownTag}
7240
7872
  appearance="${this.onDark ? 'inverse' : this.appearance}"
7873
+ .a11yRole=${"combobox"}
7241
7874
  ?autoPlacement="${this.autoPlacement}"
7242
7875
  ?error="${this.validity !== undefined && this.validity !== 'valid'}"
7243
7876
  ?matchWidth="${this.matchWidth}"
@@ -7251,16 +7884,16 @@ class AuroSelect extends AuroElement$1 {
7251
7884
  part="dropdown"
7252
7885
  shape="${this.shape}"
7253
7886
  size="${this.size}">
7254
- <div slot="trigger" aria-haspopup="true" id="triggerFocus" class="triggerContent">
7887
+ <div slot="trigger" id="triggerFocus" class="triggerContent">
7255
7888
  <div class="accents left">
7256
7889
  <slot name="typeIcon"></slot>
7257
7890
  </div>
7258
7891
  <div class="mainContent">
7259
7892
  <div class="${classMap(valueContainerClasses)}">
7260
- <label class="${classMap(this.commonLabelClasses)}">
7893
+ <span id="dropdownLabel" class="${classMap(this.commonLabelClasses)}">
7261
7894
  <slot name="label"></slot>
7262
7895
  ${this.required ? undefined : html`<slot name="optionalLabel"> (optional)</slot>`}
7263
- </label>
7896
+ </span>
7264
7897
  <div class="value body-default" id="value"></div>
7265
7898
  <div id="placeholder" class="${classMap(placeholderClass)}">
7266
7899
  ${this.placeholder}
@@ -7272,7 +7905,6 @@ class AuroSelect extends AuroElement$1 {
7272
7905
  </div>
7273
7906
  <div class="accents right"></div>
7274
7907
  </div>
7275
- <div class="menuWrapper"></div>
7276
7908
  <${this.bibtemplateTag} ?large="${this.largeFullscreenHeadline}" @close-click="${this.hideBib}">
7277
7909
  <slot name="ariaLabel.bib.close" slot="ariaLabel.close">Close</slot>
7278
7910
  <slot></slot>
@@ -7282,6 +7914,7 @@ class AuroSelect extends AuroElement$1 {
7282
7914
  </div>
7283
7915
  </${this.dropdownTag}>
7284
7916
  ${this.renderNativeSelect()}
7917
+ <span id="srAnnouncement" class="util_displayHiddenVisually" aria-live="polite" role="status"></span>
7285
7918
  </div>
7286
7919
  `;
7287
7920
  }
@@ -7322,6 +7955,7 @@ class AuroSelect extends AuroElement$1 {
7322
7955
  </div>
7323
7956
  <${this.dropdownTag}
7324
7957
  appearance="${this.onDark ? 'inverse' : this.appearance}"
7958
+ .a11yRole=${"combobox"}
7325
7959
  ?autoPlacement="${this.autoPlacement}"
7326
7960
  ?error="${this.validity !== undefined && this.validity !== 'valid'}"
7327
7961
  ?matchWidth="${!this.flexMenuWidth}"
@@ -7335,16 +7969,16 @@ class AuroSelect extends AuroElement$1 {
7335
7969
  part="dropdown"
7336
7970
  shape="${this.shape}"
7337
7971
  size="${this.size}">
7338
- <div slot="trigger" aria-haspopup="true" id="triggerFocus" class="triggerContent">
7972
+ <div slot="trigger" id="triggerFocus" class="triggerContent">
7339
7973
  <div class="accents left">
7340
7974
  <slot name="typeIcon"></slot>
7341
7975
  </div>
7342
7976
  <div class="mainContent">
7343
7977
  <div class="${classMap(valueContainerClasses)}">
7344
- <label class="${classMap(this.commonLabelClasses)}">
7978
+ <span id="dropdownLabel" class="${classMap(this.commonLabelClasses)}">
7345
7979
  <slot name="label"></slot>
7346
7980
  ${this.required ? undefined : html`<slot name="optionalLabel"> (optional)</slot>`}
7347
- </label>
7981
+ </span>
7348
7982
  <div class="${classMap(valueClasses)}" id="value"></div>
7349
7983
  <div id="placeholder" class="${classMap(placeholderClass)}">
7350
7984
  ${this.placeholder}
@@ -7356,7 +7990,6 @@ class AuroSelect extends AuroElement$1 {
7356
7990
  </div>
7357
7991
  <div class="accents right"></div>
7358
7992
  </div>
7359
- <div class="menuWrapper"></div>
7360
7993
  <${this.bibtemplateTag} ?large="${this.largeFullscreenHeadline}" @close-click="${this.hideBib}">
7361
7994
  <slot name="ariaLabel.bib.close" slot="ariaLabel.close">Close</slot>
7362
7995
  <slot></slot>
@@ -7366,6 +7999,7 @@ class AuroSelect extends AuroElement$1 {
7366
7999
  </div>
7367
8000
  </${this.dropdownTag}>
7368
8001
  ${this.renderNativeSelect()}
8002
+ <span id="srAnnouncement" class="util_displayHiddenVisually" aria-live="polite" role="status"></span>
7369
8003
  </div>
7370
8004
  `;
7371
8005
  }