@aurodesignsystem-dev/auro-formkit 0.0.0-pr1474.4 → 0.0.0-pr1475.1

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 (83) hide show
  1. package/components/bibtemplate/dist/auro-bibtemplate.d.ts +7 -0
  2. package/components/bibtemplate/dist/index.js +9 -1
  3. package/components/bibtemplate/dist/registered.js +9 -1
  4. package/components/checkbox/demo/customize.html +2 -1
  5. package/components/checkbox/demo/customize.min.js +1 -1
  6. package/components/checkbox/demo/getting-started.min.js +1 -1
  7. package/components/checkbox/demo/index.min.js +1 -1
  8. package/components/checkbox/dist/index.js +1 -1
  9. package/components/checkbox/dist/registered.js +1 -1
  10. package/components/combobox/demo/customize.html +2 -1
  11. package/components/combobox/demo/customize.md +130 -106
  12. package/components/combobox/demo/customize.min.js +209 -16
  13. package/components/combobox/demo/getting-started.min.js +209 -16
  14. package/components/combobox/demo/index.min.js +209 -16
  15. package/components/combobox/dist/index.js +209 -16
  16. package/components/combobox/dist/registered.js +209 -16
  17. package/components/counter/demo/customize.min.js +208 -15
  18. package/components/counter/demo/index.min.js +208 -15
  19. package/components/counter/demo/keyboard-behavior.md +1 -0
  20. package/components/counter/dist/index.js +10 -2
  21. package/components/counter/dist/registered.js +10 -2
  22. package/components/datepicker/demo/accessibility.md +51 -3
  23. package/components/datepicker/demo/api.md +9 -0
  24. package/components/datepicker/demo/customize.html +2 -0
  25. package/components/datepicker/demo/customize.js +19 -0
  26. package/components/datepicker/demo/customize.md +72 -8
  27. package/components/datepicker/demo/customize.min.js +25690 -0
  28. package/components/datepicker/demo/design.md +3 -1
  29. package/components/datepicker/demo/index.js +2 -1
  30. package/components/datepicker/demo/index.md +81 -1
  31. package/components/datepicker/demo/index.min.js +1223 -101
  32. package/components/datepicker/demo/keyboard-behavior.md +201 -2
  33. package/components/datepicker/demo/voiceover.md +19 -12
  34. package/components/datepicker/dist/index.js +1155 -104
  35. package/components/datepicker/dist/registered.js +1155 -104
  36. package/components/datepicker/dist/src/auro-calendar-cell.d.ts +59 -0
  37. package/components/datepicker/dist/src/auro-calendar-month.d.ts +28 -0
  38. package/components/datepicker/dist/src/auro-calendar.d.ts +84 -0
  39. package/components/datepicker/dist/src/auro-datepicker.d.ts +80 -0
  40. package/components/datepicker/dist/src/datepickerKeyboardStrategy.d.ts +5 -3
  41. package/components/dropdown/demo/accessibility.md +11 -0
  42. package/components/dropdown/demo/api.md +1 -0
  43. package/components/dropdown/demo/customize.md +3 -0
  44. package/components/dropdown/demo/customize.min.js +198 -13
  45. package/components/dropdown/demo/getting-started.min.js +198 -13
  46. package/components/dropdown/demo/index.min.js +198 -13
  47. package/components/dropdown/demo/keyboard-behavior.md +1 -0
  48. package/components/dropdown/dist/auro-dropdown.d.ts +30 -1
  49. package/components/dropdown/dist/index.js +198 -13
  50. package/components/dropdown/dist/registered.js +198 -13
  51. package/components/form/demo/customize.html +6 -6
  52. package/components/form/demo/customize.js +0 -17
  53. package/components/form/demo/customize.md +51 -125
  54. package/components/form/demo/customize.min.js +1776 -327
  55. package/components/form/demo/getting-started.min.js +1776 -291
  56. package/components/form/demo/index.min.js +1776 -291
  57. package/components/form/demo/registerDemoDeps.min.js +1769 -139
  58. package/components/form/dist/auro-form.d.ts +5 -45
  59. package/components/form/dist/index.js +7 -152
  60. package/components/form/dist/registered.js +7 -152
  61. package/components/input/demo/customize.html +2 -1
  62. package/components/input/demo/customize.min.js +1 -1
  63. package/components/input/demo/getting-started.min.js +1 -1
  64. package/components/input/demo/index.min.js +1 -1
  65. package/components/input/dist/index.js +1 -1
  66. package/components/input/dist/registered.js +1 -1
  67. package/components/radio/demo/customize.min.js +2186 -0
  68. package/components/radio/demo/demo-support.min.js +55807 -0
  69. package/components/radio/demo/getting-started.js +1 -1
  70. package/components/radio/demo/getting-started.md +1 -1
  71. package/components/radio/demo/getting-started.min.js +2205 -0
  72. package/components/radio/demo/index.min.js +1 -1
  73. package/components/radio/dist/index.js +1 -1
  74. package/components/radio/dist/registered.js +1 -1
  75. package/components/select/demo/customize.html +2 -2
  76. package/components/select/demo/customize.min.js +208 -15
  77. package/components/select/demo/getting-started.min.js +208 -15
  78. package/components/select/demo/index.min.js +208 -15
  79. package/components/select/demo/keyboard-behavior.md +1 -0
  80. package/components/select/dist/index.js +208 -15
  81. package/components/select/dist/registered.js +208 -15
  82. package/custom-elements.json +2826 -2214
  83. package/package.json +2 -2
@@ -3338,7 +3338,7 @@ function applyKeyboardStrategy(component, strategy, options = {}) {
3338
3338
  });
3339
3339
  }
3340
3340
 
3341
- var styleCss$2 = i$2`: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(:popover-open){position:fixed;overflow:visible;padding:0;border:none;margin:0;background:transparent;inset:unset;outline:none}: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}`;
3341
+ var styleCss$2 = i$2`: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([desktopmodal]:popover-open)::backdrop{background:transparent}: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(:popover-open){position:fixed;overflow:visible;padding:0;border:none;margin:0;background:transparent;inset:unset;outline:none}: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}`;
3342
3342
 
3343
3343
  var colorCss$2 = i$2`.container{background-color:var(--ds-auro-dropdownbib-container-color);box-shadow:var(--ds-auro-dropdownbib-boxshadow-color);color:var(--ds-auro-dropdownbib-text-color)}`;
3344
3344
 
@@ -3969,7 +3969,7 @@ class AuroHelpText extends i {
3969
3969
  }
3970
3970
  }
3971
3971
 
3972
- var formkitVersion = '202605122157';
3972
+ var formkitVersion = '202605182202';
3973
3973
 
3974
3974
  class AuroElement extends i {
3975
3975
  static get properties() {
@@ -4149,6 +4149,7 @@ class AuroDropdown extends AuroElement {
4149
4149
  _intializeDefaults() {
4150
4150
  this.appearance = 'default';
4151
4151
  this.chevron = false;
4152
+ this.desktopModal = false;
4152
4153
  this.disabled = false;
4153
4154
  this.disableKeyboardHandling = false;
4154
4155
  this.error = false;
@@ -4329,6 +4330,14 @@ class AuroDropdown extends AuroElement {
4329
4330
  reflect: true
4330
4331
  },
4331
4332
 
4333
+ /**
4334
+ * If declared, the dropdown will behave as a modal dialog when in a desktop viewport size.
4335
+ */
4336
+ desktopModal: {
4337
+ type: Boolean,
4338
+ reflect: true
4339
+ },
4340
+
4332
4341
  /**
4333
4342
  * If declared, the dropdown will only show by calling the API .show() public method.
4334
4343
  */
@@ -4616,6 +4625,15 @@ class AuroDropdown extends AuroElement {
4616
4625
 
4617
4626
  disconnectedCallback() {
4618
4627
  super.disconnectedCallback();
4628
+ this._clearPageInert();
4629
+ if (this._bibTabHandler) {
4630
+ this.removeEventListener('keydown', this._bibTabHandler);
4631
+ this._bibTabHandler = undefined;
4632
+ }
4633
+ if (this.focusTrap) {
4634
+ this.focusTrap.disconnect();
4635
+ this.focusTrap = undefined;
4636
+ }
4619
4637
  if (this.floater) {
4620
4638
  this.floater.hideBib('disconnect');
4621
4639
  this.floater.disconnect();
@@ -4643,19 +4661,34 @@ class AuroDropdown extends AuroElement {
4643
4661
  if (this.isPopoverVisible) {
4644
4662
  // Fullscreen: use showModal() for native accessibility (inert outside, focus trap)
4645
4663
  // Desktop: use show() for Floating UI positioning + FocusTrap for focus management
4646
- const useModal = this.isBibFullscreen;
4647
- this.bibElement.value.open(useModal);
4664
+ this.bibElement.value.open(this.isBibFullscreen);
4665
+ this.updateFocusTrap();
4666
+
4667
+ // Desktop modal: make siblings inert so content outside is not interactive
4668
+ if (this.desktopModal && !this.isBibFullscreen) {
4669
+ this._setPageInert();
4670
+ }
4648
4671
  } else {
4649
4672
  this.bibElement.value.close();
4673
+ this._clearPageInert();
4650
4674
  }
4651
4675
  }
4652
4676
 
4653
4677
  // When fullscreen strategy changes while open, re-open dialog with correct mode
4654
4678
  // (e.g. resizing from desktop → mobile while dropdown is open)
4655
4679
  if (changedProperties.has('isBibFullscreen') && this.isPopoverVisible && this.bibElement.value) {
4656
- const useModal = this.isBibFullscreen;
4657
4680
  this.bibElement.value.close();
4658
- this.bibElement.value.open(useModal);
4681
+ this.bibElement.value.open(this.isBibFullscreen);
4682
+
4683
+ // Re-initialize focus management for the new strategy
4684
+ this.updateFocusTrap();
4685
+
4686
+ // Toggle inert: desktop modal needs it, fullscreen showModal() handles it natively
4687
+ if (this.desktopModal && !this.isBibFullscreen) {
4688
+ this._setPageInert();
4689
+ } else {
4690
+ this._clearPageInert();
4691
+ }
4659
4692
  }
4660
4693
  }
4661
4694
 
@@ -4665,8 +4698,14 @@ class AuroDropdown extends AuroElement {
4665
4698
  * @param {CustomEvent} event - The custom event that contains the dropdown toggle information.
4666
4699
  */
4667
4700
  handleDropdownToggle(event) {
4668
- this.updateFocusTrap();
4669
4701
  this.isPopoverVisible = event.detail.expanded;
4702
+
4703
+ // Tear down FocusTrap when closing. Creation happens in updated()
4704
+ // after the dialog is open so getFocusableElements can find content.
4705
+ if (!this.isPopoverVisible) {
4706
+ this.updateFocusTrap();
4707
+ }
4708
+
4670
4709
  const eventType = event.detail.eventType || "unknown";
4671
4710
  if (!this.isPopoverVisible && this.hasFocus && eventType === "keydown") {
4672
4711
  this.trigger.focus();
@@ -4765,19 +4804,164 @@ class AuroDropdown extends AuroElement {
4765
4804
  * @private
4766
4805
  */
4767
4806
  updateFocusTrap() {
4807
+ // Always clean up existing handlers/traps before setting up new ones
4808
+ // to prevent duplicate listeners on repeated calls.
4809
+ if (this._bibTabHandler) {
4810
+ this.removeEventListener('keydown', this._bibTabHandler);
4811
+ this._bibTabHandler = undefined;
4812
+ }
4813
+
4814
+ if (this.focusTrap) {
4815
+ this.focusTrap.disconnect();
4816
+ this.focusTrap = undefined;
4817
+ }
4818
+
4768
4819
  if (this.isPopoverVisible) {
4769
4820
  if (!this.isBibFullscreen) {
4770
- // Desktop: show() doesn't trap focus, so use FocusTrap
4771
- this.focusTrap = new FocusTrap(this.bibContent);
4772
- this.focusTrap.focusFirstElement();
4821
+ if (this.desktopModal) {
4822
+ // Desktop modal: trap focus only within the bib content.
4823
+ // Can't use FocusTrap on the bib element because keydown events
4824
+ // from slotted content bubble through the dropdown host (light DOM),
4825
+ // not through the bib (shadow projection target). Using FocusTrap
4826
+ // on the dropdown would include the trigger in the tab cycle.
4827
+ // Instead, listen for Tab on the dropdown and manually wrap focus
4828
+ // within the bib's focusable elements.
4829
+ this._bibTabHandler = (event) => {
4830
+ if (event.key !== 'Tab') {
4831
+ return;
4832
+ }
4833
+
4834
+ // Collect focusable elements from the bib content.
4835
+ const focusables = getFocusableElements(this.bibContent);
4836
+
4837
+ // Fallback: try from slotted content directly
4838
+ if (!focusables.length) {
4839
+ const slot = this.shadowRoot.querySelector('.slotContent slot');
4840
+ const assignedNodes = slot ? slot.assignedNodes({ flatten: true }) : [];
4841
+
4842
+ for (const node of assignedNodes) {
4843
+ if (node.nodeType === Node.ELEMENT_NODE) {
4844
+ focusables.push(...getFocusableElements(node));
4845
+ }
4846
+ }
4847
+ }
4848
+
4849
+ if (!focusables.length) {
4850
+ return;
4851
+ }
4852
+
4853
+ event.preventDefault();
4854
+
4855
+ const direction = event.shiftKey ? -1 : 1; // eslint-disable-line no-magic-numbers
4856
+
4857
+ // Walk the active element chain through shadow roots
4858
+ const actives = this._getActiveElements();
4859
+
4860
+ let idx = focusables.findIndex((el) => actives.includes(el));
4861
+
4862
+ if (idx === -1) { // eslint-disable-line no-magic-numbers
4863
+ // Focus is not on a known element — move to first/last
4864
+ idx = direction === 1 ? -1 : focusables.length; // eslint-disable-line no-magic-numbers
4865
+ }
4866
+
4867
+ // Try each element in order, skipping any that can't receive focus
4868
+ // (e.g. hidden elements, elements in collapsed sections)
4869
+ for (let index = 0; index < focusables.length; index++) { // eslint-disable-line no-plusplus
4870
+ let nextIdx = idx + direction;
4871
+
4872
+ // Wrap around
4873
+ if (nextIdx < 0) {
4874
+ nextIdx = focusables.length - 1;
4875
+ } else if (nextIdx >= focusables.length) {
4876
+ nextIdx = 0;
4877
+ }
4878
+
4879
+ focusables[nextIdx].focus();
4880
+
4881
+ // Verify focus actually moved to the target
4882
+ const newActives = this._getActiveElements();
4883
+
4884
+ if (newActives.includes(focusables[nextIdx])) {
4885
+ return;
4886
+ }
4887
+
4888
+ // Focus didn't stick — skip this element and try the next
4889
+ idx = nextIdx;
4890
+ }
4891
+ };
4892
+ this.addEventListener('keydown', this._bibTabHandler);
4893
+ } else {
4894
+ // Normal desktop: use FocusTrap on the bib element
4895
+ this.focusTrap = new FocusTrap(this.bibContent);
4896
+ this.focusTrap.focusFirstElement();
4897
+ }
4773
4898
  }
4774
4899
  // Fullscreen: showModal() provides native focus trapping
4900
+ }
4901
+ }
4902
+
4903
+ /**
4904
+ * Returns the chain of active (focused) elements through shadow roots.
4905
+ * @private
4906
+ * @returns {Array<HTMLElement>}
4907
+ */
4908
+ _getActiveElements() {
4909
+ let { activeElement } = document;
4910
+ const actives = [activeElement];
4911
+
4912
+ while (activeElement?.shadowRoot?.activeElement) {
4913
+ activeElement = activeElement.shadowRoot.activeElement;
4914
+ actives.push(activeElement);
4915
+ }
4916
+
4917
+ return actives;
4918
+ }
4919
+
4920
+ /**
4921
+ * Sets `inert` on sibling elements of the dropdown's top-level host
4922
+ * so that content outside the dropdown is not interactive while the modal is open.
4923
+ * Walks up through shadow DOM boundaries to find the outermost host element
4924
+ * in the light DOM, then sets `inert` on that element's siblings.
4925
+ * @private
4926
+ */
4927
+ _setPageInert() {
4928
+ if (this._inertSiblings) {
4775
4929
  return;
4776
4930
  }
4777
4931
 
4778
- if (this.focusTrap) {
4779
- this.focusTrap.disconnect();
4780
- this.focusTrap = undefined;
4932
+ this._inertSiblings = [];
4933
+
4934
+ // Walk up through shadow DOM boundaries to find the topmost host
4935
+ // element in the light DOM. For example, if this dropdown is inside
4936
+ // auro-datepicker's shadow DOM, we walk up to the datepicker element
4937
+ // so we set inert on its siblings — not on the datepicker itself.
4938
+ let host = this;
4939
+ while (host.getRootNode() instanceof ShadowRoot) {
4940
+ host = host.getRootNode().host;
4941
+ }
4942
+
4943
+ const parent = host.parentElement;
4944
+
4945
+ if (parent) {
4946
+ for (const sibling of parent.children) {
4947
+ if (sibling !== host && !sibling.inert) {
4948
+ sibling.inert = true;
4949
+ this._inertSiblings.push(sibling);
4950
+ }
4951
+ }
4952
+ }
4953
+ }
4954
+
4955
+ /**
4956
+ * Restores `inert` state on siblings that were made inert by `_setPageInert`.
4957
+ * @private
4958
+ */
4959
+ _clearPageInert() {
4960
+ if (this._inertSiblings) {
4961
+ for (const sibling of this._inertSiblings) {
4962
+ sibling.inert = false;
4963
+ }
4964
+ this._inertSiblings = undefined;
4781
4965
  }
4782
4966
  }
4783
4967
 
@@ -5016,6 +5200,7 @@ class AuroDropdown extends AuroElement {
5016
5200
  shape="${this.shape}"
5017
5201
  ?data-show="${this.isPopoverVisible}"
5018
5202
  ?isfullscreen="${this.isBibFullscreen}"
5203
+ ?desktopmodal="${this.desktopModal}"
5019
5204
  .dialogLabel="${this.bibDialogLabel}"
5020
5205
  ${n$2(this.bibElement)}
5021
5206
  >
@@ -5,6 +5,7 @@
5
5
  <p>The trigger is a focusable element and participates in the standard tab order, responding to <code>Tab</code> and <code>Shift+Tab</code> key events per <auro-hyperlink href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/tabindex">native browser behavior</auro-hyperlink>, i.e., these keys step through the browser tabindex sequence.</p>
6
6
  <p>When the component is <code>disabled</code> it is removed from the <code>tabindex</code> sequence. VoiceOver's virtual cursor <em>(swipe navigation)</em> can still encounter the component, but standard keyboard <code>Tab</code> navigation skips it.</p>
7
7
  <p>When the bib is collapsed, the bib content is excluded from the tab sequence. When <strong>expanded</strong>, focusable elements within the bib content are included in the natural tab order. In fullscreen mode, focus is trapped within the bib, and the tab sequence cycles through the bib content focusable elements until the bib is closed or the viewport no longer meets the fullscreen condition and is rendered as a popover.</p>
8
+ <p>When the <code>desktopModal</code> attribute is set, focus is also trapped within the bib on desktop viewports. All sibling elements on the page are marked <code>inert</code>, preventing interaction with content outside the dropdown until it is closed.</p>
8
9
  <!-- AURO-GENERATED-CONTENT:END -->
9
10
  <auro-header level="3" id="keyEvents">Key Events</auro-header>
10
11
  <!-- AURO-GENERATED-CONTENT:START (FILE:src=./../docs/partials/keyEvents.md) -->
@@ -44,6 +44,13 @@ export class AuroDropdown extends AuroElement {
44
44
  type: BooleanConstructor;
45
45
  reflect: boolean;
46
46
  };
47
+ /**
48
+ * If declared, the dropdown will behave as a modal dialog when in a desktop viewport size.
49
+ */
50
+ desktopModal: {
51
+ type: BooleanConstructor;
52
+ reflect: boolean;
53
+ };
47
54
  /**
48
55
  * If declared, the dropdown will only show by calling the API .show() public method.
49
56
  */
@@ -285,6 +292,7 @@ export class AuroDropdown extends AuroElement {
285
292
  private _intializeDefaults;
286
293
  appearance: string | undefined;
287
294
  chevron: boolean | undefined;
295
+ desktopModal: boolean | undefined;
288
296
  disabled: boolean | undefined;
289
297
  disableKeyboardHandling: boolean | undefined;
290
298
  error: boolean | undefined;
@@ -373,6 +381,8 @@ export class AuroDropdown extends AuroElement {
373
381
  * @returns {string}
374
382
  */
375
383
  private get focusableEntityQuery();
384
+ _bibTabHandler: ((event: any) => void) | undefined;
385
+ focusTrap: any;
376
386
  updated(changedProperties: any): void;
377
387
  firstUpdated(): void;
378
388
  dropdownId: any;
@@ -400,7 +410,26 @@ export class AuroDropdown extends AuroElement {
400
410
  * @private
401
411
  */
402
412
  private updateFocusTrap;
403
- focusTrap: any;
413
+ /**
414
+ * Returns the chain of active (focused) elements through shadow roots.
415
+ * @private
416
+ * @returns {Array<HTMLElement>}
417
+ */
418
+ private _getActiveElements;
419
+ /**
420
+ * Sets `inert` on sibling elements of the dropdown's top-level host
421
+ * so that content outside the dropdown is not interactive while the modal is open.
422
+ * Walks up through shadow DOM boundaries to find the outermost host element
423
+ * in the light DOM, then sets `inert` on that element's siblings.
424
+ * @private
425
+ */
426
+ private _setPageInert;
427
+ _inertSiblings: any[] | undefined;
428
+ /**
429
+ * Restores `inert` state on siblings that were made inert by `_setPageInert`.
430
+ * @private
431
+ */
432
+ private _clearPageInert;
404
433
  /**
405
434
  * Function to support @focusout event.
406
435
  * @private
@@ -3243,7 +3243,7 @@ function applyKeyboardStrategy(component, strategy, options = {}) {
3243
3243
  });
3244
3244
  }
3245
3245
 
3246
- var styleCss$2 = 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(:popover-open){position:fixed;overflow:visible;padding:0;border:none;margin:0;background:transparent;inset:unset;outline:none}: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}`;
3246
+ var styleCss$2 = 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([desktopmodal]:popover-open)::backdrop{background:transparent}: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(:popover-open){position:fixed;overflow:visible;padding:0;border:none;margin:0;background:transparent;inset:unset;outline:none}: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}`;
3247
3247
 
3248
3248
  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)}`;
3249
3249
 
@@ -3874,7 +3874,7 @@ class AuroHelpText extends LitElement {
3874
3874
  }
3875
3875
  }
3876
3876
 
3877
- var formkitVersion = '202605122157';
3877
+ var formkitVersion = '202605182202';
3878
3878
 
3879
3879
  class AuroElement extends LitElement {
3880
3880
  static get properties() {
@@ -4054,6 +4054,7 @@ class AuroDropdown extends AuroElement {
4054
4054
  _intializeDefaults() {
4055
4055
  this.appearance = 'default';
4056
4056
  this.chevron = false;
4057
+ this.desktopModal = false;
4057
4058
  this.disabled = false;
4058
4059
  this.disableKeyboardHandling = false;
4059
4060
  this.error = false;
@@ -4234,6 +4235,14 @@ class AuroDropdown extends AuroElement {
4234
4235
  reflect: true
4235
4236
  },
4236
4237
 
4238
+ /**
4239
+ * If declared, the dropdown will behave as a modal dialog when in a desktop viewport size.
4240
+ */
4241
+ desktopModal: {
4242
+ type: Boolean,
4243
+ reflect: true
4244
+ },
4245
+
4237
4246
  /**
4238
4247
  * If declared, the dropdown will only show by calling the API .show() public method.
4239
4248
  */
@@ -4521,6 +4530,15 @@ class AuroDropdown extends AuroElement {
4521
4530
 
4522
4531
  disconnectedCallback() {
4523
4532
  super.disconnectedCallback();
4533
+ this._clearPageInert();
4534
+ if (this._bibTabHandler) {
4535
+ this.removeEventListener('keydown', this._bibTabHandler);
4536
+ this._bibTabHandler = undefined;
4537
+ }
4538
+ if (this.focusTrap) {
4539
+ this.focusTrap.disconnect();
4540
+ this.focusTrap = undefined;
4541
+ }
4524
4542
  if (this.floater) {
4525
4543
  this.floater.hideBib('disconnect');
4526
4544
  this.floater.disconnect();
@@ -4548,19 +4566,34 @@ class AuroDropdown extends AuroElement {
4548
4566
  if (this.isPopoverVisible) {
4549
4567
  // Fullscreen: use showModal() for native accessibility (inert outside, focus trap)
4550
4568
  // Desktop: use show() for Floating UI positioning + FocusTrap for focus management
4551
- const useModal = this.isBibFullscreen;
4552
- this.bibElement.value.open(useModal);
4569
+ this.bibElement.value.open(this.isBibFullscreen);
4570
+ this.updateFocusTrap();
4571
+
4572
+ // Desktop modal: make siblings inert so content outside is not interactive
4573
+ if (this.desktopModal && !this.isBibFullscreen) {
4574
+ this._setPageInert();
4575
+ }
4553
4576
  } else {
4554
4577
  this.bibElement.value.close();
4578
+ this._clearPageInert();
4555
4579
  }
4556
4580
  }
4557
4581
 
4558
4582
  // When fullscreen strategy changes while open, re-open dialog with correct mode
4559
4583
  // (e.g. resizing from desktop → mobile while dropdown is open)
4560
4584
  if (changedProperties.has('isBibFullscreen') && this.isPopoverVisible && this.bibElement.value) {
4561
- const useModal = this.isBibFullscreen;
4562
4585
  this.bibElement.value.close();
4563
- this.bibElement.value.open(useModal);
4586
+ this.bibElement.value.open(this.isBibFullscreen);
4587
+
4588
+ // Re-initialize focus management for the new strategy
4589
+ this.updateFocusTrap();
4590
+
4591
+ // Toggle inert: desktop modal needs it, fullscreen showModal() handles it natively
4592
+ if (this.desktopModal && !this.isBibFullscreen) {
4593
+ this._setPageInert();
4594
+ } else {
4595
+ this._clearPageInert();
4596
+ }
4564
4597
  }
4565
4598
  }
4566
4599
 
@@ -4570,8 +4603,14 @@ class AuroDropdown extends AuroElement {
4570
4603
  * @param {CustomEvent} event - The custom event that contains the dropdown toggle information.
4571
4604
  */
4572
4605
  handleDropdownToggle(event) {
4573
- this.updateFocusTrap();
4574
4606
  this.isPopoverVisible = event.detail.expanded;
4607
+
4608
+ // Tear down FocusTrap when closing. Creation happens in updated()
4609
+ // after the dialog is open so getFocusableElements can find content.
4610
+ if (!this.isPopoverVisible) {
4611
+ this.updateFocusTrap();
4612
+ }
4613
+
4575
4614
  const eventType = event.detail.eventType || "unknown";
4576
4615
  if (!this.isPopoverVisible && this.hasFocus && eventType === "keydown") {
4577
4616
  this.trigger.focus();
@@ -4670,19 +4709,164 @@ class AuroDropdown extends AuroElement {
4670
4709
  * @private
4671
4710
  */
4672
4711
  updateFocusTrap() {
4712
+ // Always clean up existing handlers/traps before setting up new ones
4713
+ // to prevent duplicate listeners on repeated calls.
4714
+ if (this._bibTabHandler) {
4715
+ this.removeEventListener('keydown', this._bibTabHandler);
4716
+ this._bibTabHandler = undefined;
4717
+ }
4718
+
4719
+ if (this.focusTrap) {
4720
+ this.focusTrap.disconnect();
4721
+ this.focusTrap = undefined;
4722
+ }
4723
+
4673
4724
  if (this.isPopoverVisible) {
4674
4725
  if (!this.isBibFullscreen) {
4675
- // Desktop: show() doesn't trap focus, so use FocusTrap
4676
- this.focusTrap = new FocusTrap(this.bibContent);
4677
- this.focusTrap.focusFirstElement();
4726
+ if (this.desktopModal) {
4727
+ // Desktop modal: trap focus only within the bib content.
4728
+ // Can't use FocusTrap on the bib element because keydown events
4729
+ // from slotted content bubble through the dropdown host (light DOM),
4730
+ // not through the bib (shadow projection target). Using FocusTrap
4731
+ // on the dropdown would include the trigger in the tab cycle.
4732
+ // Instead, listen for Tab on the dropdown and manually wrap focus
4733
+ // within the bib's focusable elements.
4734
+ this._bibTabHandler = (event) => {
4735
+ if (event.key !== 'Tab') {
4736
+ return;
4737
+ }
4738
+
4739
+ // Collect focusable elements from the bib content.
4740
+ const focusables = getFocusableElements(this.bibContent);
4741
+
4742
+ // Fallback: try from slotted content directly
4743
+ if (!focusables.length) {
4744
+ const slot = this.shadowRoot.querySelector('.slotContent slot');
4745
+ const assignedNodes = slot ? slot.assignedNodes({ flatten: true }) : [];
4746
+
4747
+ for (const node of assignedNodes) {
4748
+ if (node.nodeType === Node.ELEMENT_NODE) {
4749
+ focusables.push(...getFocusableElements(node));
4750
+ }
4751
+ }
4752
+ }
4753
+
4754
+ if (!focusables.length) {
4755
+ return;
4756
+ }
4757
+
4758
+ event.preventDefault();
4759
+
4760
+ const direction = event.shiftKey ? -1 : 1; // eslint-disable-line no-magic-numbers
4761
+
4762
+ // Walk the active element chain through shadow roots
4763
+ const actives = this._getActiveElements();
4764
+
4765
+ let idx = focusables.findIndex((el) => actives.includes(el));
4766
+
4767
+ if (idx === -1) { // eslint-disable-line no-magic-numbers
4768
+ // Focus is not on a known element — move to first/last
4769
+ idx = direction === 1 ? -1 : focusables.length; // eslint-disable-line no-magic-numbers
4770
+ }
4771
+
4772
+ // Try each element in order, skipping any that can't receive focus
4773
+ // (e.g. hidden elements, elements in collapsed sections)
4774
+ for (let index = 0; index < focusables.length; index++) { // eslint-disable-line no-plusplus
4775
+ let nextIdx = idx + direction;
4776
+
4777
+ // Wrap around
4778
+ if (nextIdx < 0) {
4779
+ nextIdx = focusables.length - 1;
4780
+ } else if (nextIdx >= focusables.length) {
4781
+ nextIdx = 0;
4782
+ }
4783
+
4784
+ focusables[nextIdx].focus();
4785
+
4786
+ // Verify focus actually moved to the target
4787
+ const newActives = this._getActiveElements();
4788
+
4789
+ if (newActives.includes(focusables[nextIdx])) {
4790
+ return;
4791
+ }
4792
+
4793
+ // Focus didn't stick — skip this element and try the next
4794
+ idx = nextIdx;
4795
+ }
4796
+ };
4797
+ this.addEventListener('keydown', this._bibTabHandler);
4798
+ } else {
4799
+ // Normal desktop: use FocusTrap on the bib element
4800
+ this.focusTrap = new FocusTrap(this.bibContent);
4801
+ this.focusTrap.focusFirstElement();
4802
+ }
4678
4803
  }
4679
4804
  // Fullscreen: showModal() provides native focus trapping
4805
+ }
4806
+ }
4807
+
4808
+ /**
4809
+ * Returns the chain of active (focused) elements through shadow roots.
4810
+ * @private
4811
+ * @returns {Array<HTMLElement>}
4812
+ */
4813
+ _getActiveElements() {
4814
+ let { activeElement } = document;
4815
+ const actives = [activeElement];
4816
+
4817
+ while (activeElement?.shadowRoot?.activeElement) {
4818
+ activeElement = activeElement.shadowRoot.activeElement;
4819
+ actives.push(activeElement);
4820
+ }
4821
+
4822
+ return actives;
4823
+ }
4824
+
4825
+ /**
4826
+ * Sets `inert` on sibling elements of the dropdown's top-level host
4827
+ * so that content outside the dropdown is not interactive while the modal is open.
4828
+ * Walks up through shadow DOM boundaries to find the outermost host element
4829
+ * in the light DOM, then sets `inert` on that element's siblings.
4830
+ * @private
4831
+ */
4832
+ _setPageInert() {
4833
+ if (this._inertSiblings) {
4680
4834
  return;
4681
4835
  }
4682
4836
 
4683
- if (this.focusTrap) {
4684
- this.focusTrap.disconnect();
4685
- this.focusTrap = undefined;
4837
+ this._inertSiblings = [];
4838
+
4839
+ // Walk up through shadow DOM boundaries to find the topmost host
4840
+ // element in the light DOM. For example, if this dropdown is inside
4841
+ // auro-datepicker's shadow DOM, we walk up to the datepicker element
4842
+ // so we set inert on its siblings — not on the datepicker itself.
4843
+ let host = this;
4844
+ while (host.getRootNode() instanceof ShadowRoot) {
4845
+ host = host.getRootNode().host;
4846
+ }
4847
+
4848
+ const parent = host.parentElement;
4849
+
4850
+ if (parent) {
4851
+ for (const sibling of parent.children) {
4852
+ if (sibling !== host && !sibling.inert) {
4853
+ sibling.inert = true;
4854
+ this._inertSiblings.push(sibling);
4855
+ }
4856
+ }
4857
+ }
4858
+ }
4859
+
4860
+ /**
4861
+ * Restores `inert` state on siblings that were made inert by `_setPageInert`.
4862
+ * @private
4863
+ */
4864
+ _clearPageInert() {
4865
+ if (this._inertSiblings) {
4866
+ for (const sibling of this._inertSiblings) {
4867
+ sibling.inert = false;
4868
+ }
4869
+ this._inertSiblings = undefined;
4686
4870
  }
4687
4871
  }
4688
4872
 
@@ -4921,6 +5105,7 @@ class AuroDropdown extends AuroElement {
4921
5105
  shape="${this.shape}"
4922
5106
  ?data-show="${this.isPopoverVisible}"
4923
5107
  ?isfullscreen="${this.isBibFullscreen}"
5108
+ ?desktopmodal="${this.desktopModal}"
4924
5109
  .dialogLabel="${this.bibDialogLabel}"
4925
5110
  ${ref(this.bibElement)}
4926
5111
  >