@aurodesignsystem-dev/auro-formkit 0.0.0-pr1492.4 → 0.0.0-pr1493.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 (153) 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/css-only.html +57 -0
  5. package/components/checkbox/demo/css-only.md +146 -0
  6. package/components/checkbox/demo/customize.min.js +2 -2
  7. package/components/checkbox/demo/getting-started.min.js +2 -2
  8. package/components/checkbox/demo/index.min.js +2 -2
  9. package/components/checkbox/demo/pages.json +1 -1
  10. package/components/checkbox/demo/styles.min.css +1 -1
  11. package/components/checkbox/demo/why-checkbox.html +57 -0
  12. package/components/checkbox/demo/why-checkbox.md +86 -0
  13. package/components/checkbox/dist/index.js +2 -2
  14. package/components/checkbox/dist/registered.js +2 -2
  15. package/components/combobox/demo/css-only.html +57 -0
  16. package/components/combobox/demo/css-only.md +209 -0
  17. package/components/combobox/demo/customize.md +7 -3
  18. package/components/combobox/demo/customize.min.js +1846 -1804
  19. package/components/combobox/demo/getting-started.min.js +1846 -1804
  20. package/components/combobox/demo/index.min.js +1846 -1804
  21. package/components/combobox/demo/pages.json +1 -1
  22. package/components/combobox/demo/styles.min.css +1 -1
  23. package/components/combobox/demo/why-combobox.html +57 -0
  24. package/components/combobox/demo/why-combobox.md +113 -0
  25. package/components/combobox/dist/auro-combobox.d.ts +5 -0
  26. package/components/combobox/dist/index.js +796 -179
  27. package/components/combobox/dist/registered.js +796 -179
  28. package/components/counter/demo/css-only.html +57 -0
  29. package/components/counter/demo/css-only.md +184 -0
  30. package/components/counter/demo/customize.min.js +276 -16
  31. package/components/counter/demo/index.min.js +276 -16
  32. package/components/counter/demo/keyboard-behavior.md +1 -0
  33. package/components/counter/demo/pages.json +1 -1
  34. package/components/counter/demo/styles.min.css +1 -1
  35. package/components/counter/demo/why-counter.html +57 -0
  36. package/components/counter/demo/why-counter.md +108 -0
  37. package/components/counter/dist/index.js +5457 -193
  38. package/components/counter/dist/registered.js +5457 -193
  39. package/components/datepicker/demo/accessibility.md +54 -3
  40. package/components/datepicker/demo/api.md +65 -55
  41. package/components/datepicker/demo/css-only.html +57 -0
  42. package/components/datepicker/demo/css-only.md +151 -0
  43. package/components/datepicker/demo/customize.html +2 -0
  44. package/components/datepicker/demo/customize.js +19 -0
  45. package/components/datepicker/demo/customize.md +474 -298
  46. package/components/datepicker/demo/customize.min.js +36243 -0
  47. package/components/datepicker/demo/design.md +5 -3
  48. package/components/datepicker/demo/getting-started.md +6 -6
  49. package/components/datepicker/demo/index.js +5 -1
  50. package/components/datepicker/demo/index.md +113 -16
  51. package/components/datepicker/demo/index.min.js +2736 -398
  52. package/components/datepicker/demo/keyboard-behavior.md +201 -2
  53. package/components/datepicker/demo/pages.json +1 -1
  54. package/components/datepicker/demo/styles.min.css +1 -1
  55. package/components/datepicker/demo/voiceover.md +21 -12
  56. package/components/datepicker/demo/why-datepicker.html +57 -0
  57. package/components/datepicker/demo/why-datepicker.md +133 -0
  58. package/components/datepicker/dist/index.js +2650 -399
  59. package/components/datepicker/dist/registered.js +2650 -399
  60. package/components/datepicker/dist/src/auro-calendar-cell.d.ts +116 -14
  61. package/components/datepicker/dist/src/auro-calendar-month.d.ts +54 -1
  62. package/components/datepicker/dist/src/auro-calendar.d.ts +264 -0
  63. package/components/datepicker/dist/src/auro-datepicker.d.ts +189 -53
  64. package/components/datepicker/dist/src/datepickerKeyboardStrategy.d.ts +5 -3
  65. package/components/datepicker/dist/src/utilities.d.ts +0 -14
  66. package/components/dropdown/demo/accessibility.md +11 -0
  67. package/components/dropdown/demo/api.md +1 -0
  68. package/components/dropdown/demo/css-only.html +57 -0
  69. package/components/dropdown/demo/css-only.md +219 -0
  70. package/components/dropdown/demo/customize.md +3 -0
  71. package/components/dropdown/demo/customize.min.js +265 -13
  72. package/components/dropdown/demo/getting-started.min.js +265 -13
  73. package/components/dropdown/demo/index.min.js +265 -13
  74. package/components/dropdown/demo/keyboard-behavior.md +1 -0
  75. package/components/dropdown/demo/pages.json +1 -1
  76. package/components/dropdown/demo/styles.min.css +1 -1
  77. package/components/dropdown/demo/why-dropdown.html +57 -0
  78. package/components/dropdown/demo/why-dropdown.md +97 -0
  79. package/components/dropdown/dist/auro-dropdown.d.ts +36 -1
  80. package/components/dropdown/dist/index.js +265 -13
  81. package/components/dropdown/dist/registered.js +265 -13
  82. package/components/form/demo/css-only.html +57 -0
  83. package/components/form/demo/css-only.md +156 -0
  84. package/components/form/demo/customize.min.js +11085 -8074
  85. package/components/form/demo/getting-started.min.js +11085 -8074
  86. package/components/form/demo/index.min.js +11085 -8074
  87. package/components/form/demo/pages.json +1 -1
  88. package/components/form/demo/registerDemoDeps.min.js +11084 -8073
  89. package/components/form/demo/styles.min.css +1 -1
  90. package/components/form/demo/why-form.html +57 -0
  91. package/components/form/demo/why-form.md +101 -0
  92. package/components/input/demo/css-only.html +57 -0
  93. package/components/input/demo/css-only.md +199 -0
  94. package/components/input/demo/customize.md +625 -636
  95. package/components/input/demo/customize.min.js +150 -75
  96. package/components/input/demo/getting-started.md +16 -6
  97. package/components/input/demo/getting-started.min.js +150 -75
  98. package/components/input/demo/index.min.js +150 -75
  99. package/components/input/demo/pages.json +1 -1
  100. package/components/input/demo/styles.min.css +1 -1
  101. package/components/input/demo/why-input.html +57 -0
  102. package/components/input/demo/why-input.md +121 -0
  103. package/components/input/dist/auro-input-util.d.ts +1 -1
  104. package/components/input/dist/base-input.d.ts +16 -0
  105. package/components/input/dist/index.js +150 -75
  106. package/components/input/dist/registered.js +150 -75
  107. package/components/input/dist/utilities.d.ts +1 -2
  108. package/components/menu/demo/api.md +41 -45
  109. package/components/menu/demo/css-only.html +57 -0
  110. package/components/menu/demo/css-only.md +166 -0
  111. package/components/menu/demo/customize.md +0 -28
  112. package/components/menu/demo/index.min.js +779 -1354
  113. package/components/menu/demo/pages.json +1 -1
  114. package/components/menu/demo/styles.min.css +1 -1
  115. package/components/menu/demo/why-menu.html +57 -0
  116. package/components/menu/demo/why-menu.md +104 -0
  117. package/components/menu/dist/auro-menu.d.ts +95 -110
  118. package/components/menu/dist/auro-menuoption.d.ts +32 -138
  119. package/components/menu/dist/index.js +753 -1308
  120. package/components/menu/dist/registered.js +765 -1308
  121. package/components/radio/demo/css-only.html +57 -0
  122. package/components/radio/demo/css-only.md +151 -0
  123. package/components/radio/demo/customize.min.js +1764 -0
  124. package/components/radio/demo/demo-support.min.js +55807 -0
  125. package/components/radio/demo/getting-started.js +1 -1
  126. package/components/radio/demo/getting-started.md +1 -1
  127. package/components/radio/demo/getting-started.min.js +1783 -0
  128. package/components/radio/demo/index.min.js +2 -2
  129. package/components/radio/demo/pages.json +1 -1
  130. package/components/radio/demo/styles.min.css +1 -1
  131. package/components/radio/demo/why-radio.html +57 -0
  132. package/components/radio/demo/why-radio.md +92 -0
  133. package/components/radio/dist/index.js +2 -2
  134. package/components/radio/dist/registered.js +2 -2
  135. package/components/select/demo/api.md +1 -0
  136. package/components/select/demo/css-only.html +57 -0
  137. package/components/select/demo/css-only.md +203 -0
  138. package/components/select/demo/customize.md +78 -3
  139. package/components/select/demo/customize.min.js +1341 -1533
  140. package/components/select/demo/getting-started.min.js +1341 -1533
  141. package/components/select/demo/index.min.js +1341 -1533
  142. package/components/select/demo/keyboard-behavior.md +19 -0
  143. package/components/select/demo/pages.json +1 -1
  144. package/components/select/demo/styles.min.css +1 -1
  145. package/components/select/demo/why-select.html +57 -0
  146. package/components/select/demo/why-select.md +128 -0
  147. package/components/select/dist/auro-select.d.ts +38 -2
  148. package/components/select/dist/index.js +435 -52
  149. package/components/select/dist/registered.js +435 -52
  150. package/components/select/dist/selectUtils.d.ts +12 -0
  151. package/custom-elements.json +3051 -2553
  152. package/package.json +8 -8
  153. package/components/menu/dist/auro-menu.context.d.ts +0 -238
@@ -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,32 @@ export class AuroDropdown extends AuroElement {
400
410
  * @private
401
411
  */
402
412
  private updateFocusTrap;
403
- focusTrap: any;
413
+ _noHideOverridden: boolean | undefined;
414
+ _priorNoHide: any;
415
+ /**
416
+ * Returns the chain of active (focused) elements through shadow roots.
417
+ * @private
418
+ * @returns {Array<HTMLElement>}
419
+ */
420
+ private _getActiveElements;
421
+ /**
422
+ * Sets `inert` on sibling elements of the dropdown's top-level host
423
+ * so that content outside the dropdown is not interactive while the modal is open.
424
+ * Walks up through shadow DOM boundaries to find the outermost host element
425
+ * in the light DOM, then sets `inert` on siblings at each ancestor level
426
+ * to ensure all page content outside the host subtree is inert.
427
+ * @private
428
+ */
429
+ private _setPageInert;
430
+ _inertSiblings: any[] | undefined;
431
+ /**
432
+ * Restores `inert` state on siblings that were tracked by `_setPageInert`.
433
+ * Uses reference counting so inert is only cleared when the last modal
434
+ * dropdown releases a given element. Preserves the original inert state
435
+ * so externally-inerted elements are not inadvertently re-enabled.
436
+ * @private
437
+ */
438
+ private _clearPageInert;
404
439
  /**
405
440
  * Function to support @focusout event.
406
441
  * @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 = '202606031929';
3877
+ var formkitVersion = '202606181613';
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,45 @@ 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
+ }
4597
+ }
4598
+
4599
+ // Handle desktopModal toggled while the dropdown is already open.
4600
+ // Re-initialize focus trapping and page inert state to match the new mode.
4601
+ if (changedProperties.has('desktopModal') && this.isPopoverVisible && !this.isBibFullscreen) {
4602
+ this.updateFocusTrap();
4603
+ if (this.desktopModal) {
4604
+ this._setPageInert();
4605
+ } else {
4606
+ this._clearPageInert();
4607
+ }
4564
4608
  }
4565
4609
  }
4566
4610
 
@@ -4570,8 +4614,14 @@ class AuroDropdown extends AuroElement {
4570
4614
  * @param {CustomEvent} event - The custom event that contains the dropdown toggle information.
4571
4615
  */
4572
4616
  handleDropdownToggle(event) {
4573
- this.updateFocusTrap();
4574
4617
  this.isPopoverVisible = event.detail.expanded;
4618
+
4619
+ // Tear down FocusTrap when closing. Creation happens in updated()
4620
+ // after the dialog is open so getFocusableElements can find content.
4621
+ if (!this.isPopoverVisible) {
4622
+ this.updateFocusTrap();
4623
+ }
4624
+
4575
4625
  const eventType = event.detail.eventType || "unknown";
4576
4626
  if (!this.isPopoverVisible && this.hasFocus && eventType === "keydown") {
4577
4627
  this.trigger.focus();
@@ -4670,19 +4720,220 @@ class AuroDropdown extends AuroElement {
4670
4720
  * @private
4671
4721
  */
4672
4722
  updateFocusTrap() {
4723
+ // Always clean up existing handlers/traps before setting up new ones
4724
+ // to prevent duplicate listeners on repeated calls.
4725
+ if (this._bibTabHandler) {
4726
+ this.removeEventListener('keydown', this._bibTabHandler);
4727
+ this._bibTabHandler = undefined;
4728
+ }
4729
+
4730
+ if (this.focusTrap) {
4731
+ this.focusTrap.disconnect();
4732
+ this.focusTrap = undefined;
4733
+ }
4734
+
4735
+ // Restore the user-set noHideOnThisFocusLoss value, if we overrode it.
4736
+ if (this._noHideOverridden) {
4737
+ this.noHideOnThisFocusLoss = this._priorNoHide;
4738
+ this._noHideOverridden = false;
4739
+ this._priorNoHide = undefined;
4740
+ }
4741
+
4673
4742
  if (this.isPopoverVisible) {
4674
4743
  if (!this.isBibFullscreen) {
4675
- // Desktop: show() doesn't trap focus, so use FocusTrap
4676
- this.focusTrap = new FocusTrap(this.bibContent);
4677
- this.focusTrap.focusFirstElement();
4744
+ if (this.desktopModal) {
4745
+ // The floater's focus-loss check uses :focus-within, which does not
4746
+ // match the host when focus is on a slotted element projected through
4747
+ // multiple shadow roots. Without this override, focusing a button
4748
+ // inside the bib would be misread as focus leaving the dropdown and
4749
+ // close the bib immediately. desktopModal traps focus inside the
4750
+ // bib and inerts siblings, so suppressing focus-loss dismissal here
4751
+ // is safe — Escape and outside-click still close the bib.
4752
+ this._priorNoHide = this.noHideOnThisFocusLoss;
4753
+ this._noHideOverridden = true;
4754
+ this.noHideOnThisFocusLoss = true;
4755
+
4756
+ // Desktop modal: trap focus only within the bib content.
4757
+ // Can't use FocusTrap on the bib element because keydown events
4758
+ // from slotted content bubble through the dropdown host (light DOM),
4759
+ // not through the bib (shadow projection target). Using FocusTrap
4760
+ // on the dropdown would include the trigger in the tab cycle.
4761
+ // Instead, listen for Tab on the dropdown and manually wrap focus
4762
+ // within the bib's focusable elements.
4763
+ this._bibTabHandler = (event) => {
4764
+ if (event.key !== 'Tab') {
4765
+ return;
4766
+ }
4767
+
4768
+ // Collect focusable elements from the bib content.
4769
+ const focusables = getFocusableElements(this.bibContent);
4770
+
4771
+ // Fallback: try from slotted content directly
4772
+ if (!focusables.length) {
4773
+ const slot = this.shadowRoot.querySelector('.slotContent slot');
4774
+ const assignedNodes = slot.assignedNodes({ flatten: true });
4775
+
4776
+ for (const node of assignedNodes) {
4777
+ if (node.nodeType === Node.ELEMENT_NODE) {
4778
+ focusables.push(...getFocusableElements(node));
4779
+ }
4780
+ }
4781
+ }
4782
+
4783
+ if (!focusables.length) {
4784
+ return;
4785
+ }
4786
+
4787
+ event.preventDefault();
4788
+
4789
+ const direction = event.shiftKey ? -1 : 1; // eslint-disable-line no-magic-numbers
4790
+
4791
+ // Walk the active element chain through shadow roots
4792
+ const actives = this._getActiveElements();
4793
+
4794
+ let idx = focusables.findIndex((el) => actives.includes(el));
4795
+
4796
+ if (idx === -1) { // eslint-disable-line no-magic-numbers
4797
+ // Focus is not on a known element — move to first/last
4798
+ idx = direction === 1 ? -1 : focusables.length; // eslint-disable-line no-magic-numbers
4799
+ }
4800
+
4801
+ // Try each element in order, skipping any that can't receive focus
4802
+ // (e.g. hidden elements, elements in collapsed sections)
4803
+ for (let index = 0; index < focusables.length; index++) { // eslint-disable-line no-plusplus
4804
+ let nextIdx = idx + direction;
4805
+
4806
+ // Wrap around
4807
+ if (nextIdx < 0) {
4808
+ nextIdx = focusables.length - 1;
4809
+ } else if (nextIdx >= focusables.length) {
4810
+ nextIdx = 0;
4811
+ }
4812
+
4813
+ focusables[nextIdx].focus();
4814
+
4815
+ // Verify focus actually moved to the target
4816
+ const newActives = this._getActiveElements();
4817
+
4818
+ if (newActives.includes(focusables[nextIdx])) {
4819
+ return;
4820
+ }
4821
+
4822
+ // Focus didn't stick — skip this element and try the next
4823
+ idx = nextIdx;
4824
+ }
4825
+ };
4826
+ this.addEventListener('keydown', this._bibTabHandler);
4827
+
4828
+ // Move initial focus into the bib content, matching FocusTrap behavior
4829
+ requestAnimationFrame(() => {
4830
+ const focusables = getFocusableElements(this.bibContent);
4831
+ if (focusables.length) {
4832
+ focusables[0].focus();
4833
+ }
4834
+ });
4835
+ } else {
4836
+ // Normal desktop: use FocusTrap on the bib element.
4837
+ // Defer focusFirstElement to the next frame because the popover
4838
+ // is positioned asynchronously by Floating UI — calling focus()
4839
+ // synchronously here would target elements with zero dimensions
4840
+ // and the focus call would be silently ignored.
4841
+ this.focusTrap = new FocusTrap(this.bibContent);
4842
+ requestAnimationFrame(() => {
4843
+ if (this.focusTrap) {
4844
+ this.focusTrap.focusFirstElement();
4845
+ }
4846
+ });
4847
+ }
4678
4848
  }
4679
4849
  // Fullscreen: showModal() provides native focus trapping
4850
+ }
4851
+ }
4852
+
4853
+ /**
4854
+ * Returns the chain of active (focused) elements through shadow roots.
4855
+ * @private
4856
+ * @returns {Array<HTMLElement>}
4857
+ */
4858
+ _getActiveElements() {
4859
+ let { activeElement } = document;
4860
+ const actives = [activeElement];
4861
+
4862
+ while (activeElement?.shadowRoot?.activeElement) {
4863
+ activeElement = activeElement.shadowRoot.activeElement;
4864
+ actives.push(activeElement);
4865
+ }
4866
+
4867
+ return actives;
4868
+ }
4869
+
4870
+ /**
4871
+ * Sets `inert` on sibling elements of the dropdown's top-level host
4872
+ * so that content outside the dropdown is not interactive while the modal is open.
4873
+ * Walks up through shadow DOM boundaries to find the outermost host element
4874
+ * in the light DOM, then sets `inert` on siblings at each ancestor level
4875
+ * to ensure all page content outside the host subtree is inert.
4876
+ * @private
4877
+ */
4878
+ _setPageInert() {
4879
+ if (this._inertSiblings) {
4680
4880
  return;
4681
4881
  }
4682
4882
 
4683
- if (this.focusTrap) {
4684
- this.focusTrap.disconnect();
4685
- this.focusTrap = undefined;
4883
+ this._inertSiblings = [];
4884
+
4885
+ // Walk up through shadow DOM boundaries to find the topmost host
4886
+ // element in the light DOM. For example, if this dropdown is inside
4887
+ // auro-datepicker's shadow DOM, we walk up to the datepicker element
4888
+ // so we set inert on its siblings — not on the datepicker itself.
4889
+ let host = this;
4890
+ while (host.getRootNode() instanceof ShadowRoot) {
4891
+ host = host.getRootNode().host;
4892
+ }
4893
+
4894
+ // Walk up the ancestor chain, inerting siblings at each level
4895
+ // to ensure the entire page outside the host subtree is inert.
4896
+ // Uses a reference counter (data-auro-inert-count) so multiple
4897
+ // simultaneous modal dropdowns share inert state safely.
4898
+ let current = host;
4899
+ while (current.parentElement) {
4900
+ const parent = current.parentElement;
4901
+ for (const sibling of parent.children) {
4902
+ if (sibling !== current) {
4903
+ const count = parseInt(sibling.dataset.auroInertCount || '0', 10);
4904
+ if (count === 0) {
4905
+ sibling.dataset.auroInertWas = sibling.inert ? 'true' : 'false';
4906
+ }
4907
+ sibling.dataset.auroInertCount = String(count + 1);
4908
+ sibling.inert = true;
4909
+ this._inertSiblings.push(sibling);
4910
+ }
4911
+ }
4912
+ current = parent;
4913
+ }
4914
+ }
4915
+
4916
+ /**
4917
+ * Restores `inert` state on siblings that were tracked by `_setPageInert`.
4918
+ * Uses reference counting so inert is only cleared when the last modal
4919
+ * dropdown releases a given element. Preserves the original inert state
4920
+ * so externally-inerted elements are not inadvertently re-enabled.
4921
+ * @private
4922
+ */
4923
+ _clearPageInert() {
4924
+ if (this._inertSiblings) {
4925
+ for (const sibling of this._inertSiblings) {
4926
+ const count = parseInt(sibling.dataset.auroInertCount || '1', 10) - 1;
4927
+ if (count <= 0) {
4928
+ const wasInert = sibling.dataset.auroInertWas === 'true';
4929
+ delete sibling.dataset.auroInertCount;
4930
+ delete sibling.dataset.auroInertWas;
4931
+ sibling.inert = wasInert;
4932
+ } else {
4933
+ sibling.dataset.auroInertCount = String(count);
4934
+ }
4935
+ }
4936
+ this._inertSiblings = undefined;
4686
4937
  }
4687
4938
  }
4688
4939
 
@@ -4921,6 +5172,7 @@ class AuroDropdown extends AuroElement {
4921
5172
  shape="${this.shape}"
4922
5173
  ?data-show="${this.isPopoverVisible}"
4923
5174
  ?isfullscreen="${this.isBibFullscreen}"
5175
+ ?desktopmodal="${this.desktopModal}"
4924
5176
  .dialogLabel="${this.bibDialogLabel}"
4925
5177
  ${ref(this.bibElement)}
4926
5178
  >