@aurodesignsystem-dev/auro-formkit 0.0.0-pr1488.2 → 0.0.0-pr1489.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 (102) 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.min.js +1 -1
  5. package/components/checkbox/demo/getting-started.min.js +1 -1
  6. package/components/checkbox/demo/index.min.js +1 -1
  7. package/components/checkbox/demo/pages.json +1 -1
  8. package/components/checkbox/demo/why-checkbox.html +57 -0
  9. package/components/checkbox/demo/why-checkbox.md +86 -0
  10. package/components/checkbox/dist/index.js +1 -1
  11. package/components/checkbox/dist/registered.js +1 -1
  12. package/components/combobox/demo/customize.min.js +234 -16
  13. package/components/combobox/demo/getting-started.min.js +234 -16
  14. package/components/combobox/demo/index.min.js +234 -16
  15. package/components/combobox/demo/pages.json +1 -1
  16. package/components/combobox/demo/why-combobox.html +57 -0
  17. package/components/combobox/demo/why-combobox.md +113 -0
  18. package/components/combobox/dist/index.js +234 -16
  19. package/components/combobox/dist/registered.js +234 -16
  20. package/components/counter/demo/customize.min.js +233 -15
  21. package/components/counter/demo/index.min.js +233 -15
  22. package/components/counter/demo/keyboard-behavior.md +1 -0
  23. package/components/counter/demo/pages.json +1 -1
  24. package/components/counter/demo/why-counter.html +57 -0
  25. package/components/counter/demo/why-counter.md +108 -0
  26. package/components/counter/dist/index.js +22 -241
  27. package/components/counter/dist/registered.js +10 -2
  28. package/components/datepicker/demo/accessibility.md +51 -3
  29. package/components/datepicker/demo/api.md +11 -2
  30. package/components/datepicker/demo/customize.html +2 -0
  31. package/components/datepicker/demo/customize.js +19 -0
  32. package/components/datepicker/demo/customize.md +72 -8
  33. package/components/datepicker/demo/customize.min.js +26029 -0
  34. package/components/datepicker/demo/design.md +3 -1
  35. package/components/datepicker/demo/index.js +5 -1
  36. package/components/datepicker/demo/index.md +83 -2
  37. package/components/datepicker/demo/index.min.js +1564 -96
  38. package/components/datepicker/demo/keyboard-behavior.md +201 -2
  39. package/components/datepicker/demo/pages.json +1 -1
  40. package/components/datepicker/demo/voiceover.md +19 -12
  41. package/components/datepicker/demo/why-datepicker.html +57 -0
  42. package/components/datepicker/demo/why-datepicker.md +133 -0
  43. package/components/datepicker/dist/index.js +1489 -97
  44. package/components/datepicker/dist/registered.js +1489 -97
  45. package/components/datepicker/dist/src/auro-calendar-cell.d.ts +66 -1
  46. package/components/datepicker/dist/src/auro-calendar-month.d.ts +28 -0
  47. package/components/datepicker/dist/src/auro-calendar.d.ts +100 -0
  48. package/components/datepicker/dist/src/auro-datepicker.d.ts +88 -0
  49. package/components/datepicker/dist/src/datepickerKeyboardStrategy.d.ts +5 -3
  50. package/components/dropdown/demo/accessibility.md +11 -0
  51. package/components/dropdown/demo/api.md +1 -0
  52. package/components/dropdown/demo/customize.md +3 -0
  53. package/components/dropdown/demo/customize.min.js +223 -13
  54. package/components/dropdown/demo/getting-started.min.js +223 -13
  55. package/components/dropdown/demo/index.min.js +223 -13
  56. package/components/dropdown/demo/keyboard-behavior.md +1 -0
  57. package/components/dropdown/demo/pages.json +1 -1
  58. package/components/dropdown/demo/why-dropdown.html +57 -0
  59. package/components/dropdown/demo/why-dropdown.md +97 -0
  60. package/components/dropdown/dist/auro-dropdown.d.ts +33 -1
  61. package/components/dropdown/dist/index.js +223 -13
  62. package/components/dropdown/dist/registered.js +223 -13
  63. package/components/form/demo/customize.min.js +2461 -415
  64. package/components/form/demo/getting-started.min.js +2461 -415
  65. package/components/form/demo/index.min.js +2461 -415
  66. package/components/form/demo/pages.json +1 -1
  67. package/components/form/demo/registerDemoDeps.min.js +2457 -411
  68. package/components/form/demo/why-form.html +57 -0
  69. package/components/form/demo/why-form.md +101 -0
  70. package/components/input/demo/customize.min.js +1 -1
  71. package/components/input/demo/getting-started.min.js +1 -1
  72. package/components/input/demo/index.min.js +1 -1
  73. package/components/input/demo/pages.json +1 -1
  74. package/components/input/demo/why-input.html +57 -0
  75. package/components/input/demo/why-input.md +121 -0
  76. package/components/input/dist/index.js +1 -1
  77. package/components/input/dist/registered.js +1 -1
  78. package/components/menu/demo/pages.json +1 -1
  79. package/components/menu/demo/why-menu.html +57 -0
  80. package/components/menu/demo/why-menu.md +104 -0
  81. package/components/radio/demo/customize.min.js +2186 -0
  82. package/components/radio/demo/demo-support.min.js +55807 -0
  83. package/components/radio/demo/getting-started.js +1 -1
  84. package/components/radio/demo/getting-started.md +1 -1
  85. package/components/radio/demo/getting-started.min.js +2205 -0
  86. package/components/radio/demo/index.min.js +1 -1
  87. package/components/radio/demo/pages.json +1 -1
  88. package/components/radio/demo/why-radio.html +57 -0
  89. package/components/radio/demo/why-radio.md +92 -0
  90. package/components/radio/dist/index.js +1 -1
  91. package/components/radio/dist/registered.js +1 -1
  92. package/components/select/demo/customize.min.js +233 -15
  93. package/components/select/demo/getting-started.min.js +233 -15
  94. package/components/select/demo/index.min.js +233 -15
  95. package/components/select/demo/keyboard-behavior.md +1 -0
  96. package/components/select/demo/pages.json +1 -1
  97. package/components/select/demo/why-select.html +57 -0
  98. package/components/select/demo/why-select.md +128 -0
  99. package/components/select/dist/index.js +233 -15
  100. package/components/select/dist/registered.js +233 -15
  101. package/custom-elements.json +2183 -1466
  102. 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 = '202606011708';
3972
+ var formkitVersion = '202606011911';
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,45 @@ 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
+ }
4692
+ }
4693
+
4694
+ // Handle desktopModal toggled while the dropdown is already open.
4695
+ // Re-initialize focus trapping and page inert state to match the new mode.
4696
+ if (changedProperties.has('desktopModal') && this.isPopoverVisible && !this.isBibFullscreen) {
4697
+ this.updateFocusTrap();
4698
+ if (this.desktopModal) {
4699
+ this._setPageInert();
4700
+ } else {
4701
+ this._clearPageInert();
4702
+ }
4659
4703
  }
4660
4704
  }
4661
4705
 
@@ -4665,8 +4709,14 @@ class AuroDropdown extends AuroElement {
4665
4709
  * @param {CustomEvent} event - The custom event that contains the dropdown toggle information.
4666
4710
  */
4667
4711
  handleDropdownToggle(event) {
4668
- this.updateFocusTrap();
4669
4712
  this.isPopoverVisible = event.detail.expanded;
4713
+
4714
+ // Tear down FocusTrap when closing. Creation happens in updated()
4715
+ // after the dialog is open so getFocusableElements can find content.
4716
+ if (!this.isPopoverVisible) {
4717
+ this.updateFocusTrap();
4718
+ }
4719
+
4670
4720
  const eventType = event.detail.eventType || "unknown";
4671
4721
  if (!this.isPopoverVisible && this.hasFocus && eventType === "keydown") {
4672
4722
  this.trigger.focus();
@@ -4765,19 +4815,178 @@ class AuroDropdown extends AuroElement {
4765
4815
  * @private
4766
4816
  */
4767
4817
  updateFocusTrap() {
4818
+ // Always clean up existing handlers/traps before setting up new ones
4819
+ // to prevent duplicate listeners on repeated calls.
4820
+ if (this._bibTabHandler) {
4821
+ this.removeEventListener('keydown', this._bibTabHandler);
4822
+ this._bibTabHandler = undefined;
4823
+ }
4824
+
4825
+ if (this.focusTrap) {
4826
+ this.focusTrap.disconnect();
4827
+ this.focusTrap = undefined;
4828
+ }
4829
+
4768
4830
  if (this.isPopoverVisible) {
4769
4831
  if (!this.isBibFullscreen) {
4770
- // Desktop: show() doesn't trap focus, so use FocusTrap
4771
- this.focusTrap = new FocusTrap(this.bibContent);
4772
- this.focusTrap.focusFirstElement();
4832
+ if (this.desktopModal) {
4833
+ // Desktop modal: trap focus only within the bib content.
4834
+ // Can't use FocusTrap on the bib element because keydown events
4835
+ // from slotted content bubble through the dropdown host (light DOM),
4836
+ // not through the bib (shadow projection target). Using FocusTrap
4837
+ // on the dropdown would include the trigger in the tab cycle.
4838
+ // Instead, listen for Tab on the dropdown and manually wrap focus
4839
+ // within the bib's focusable elements.
4840
+ this._bibTabHandler = (event) => {
4841
+ if (event.key !== 'Tab') {
4842
+ return;
4843
+ }
4844
+
4845
+ // Collect focusable elements from the bib content.
4846
+ const focusables = getFocusableElements(this.bibContent);
4847
+
4848
+ // Fallback: try from slotted content directly
4849
+ if (!focusables.length) {
4850
+ const slot = this.shadowRoot.querySelector('.slotContent slot');
4851
+ const assignedNodes = slot ? slot.assignedNodes({ flatten: true }) : [];
4852
+
4853
+ for (const node of assignedNodes) {
4854
+ if (node.nodeType === Node.ELEMENT_NODE) {
4855
+ focusables.push(...getFocusableElements(node));
4856
+ }
4857
+ }
4858
+ }
4859
+
4860
+ if (!focusables.length) {
4861
+ return;
4862
+ }
4863
+
4864
+ event.preventDefault();
4865
+
4866
+ const direction = event.shiftKey ? -1 : 1; // eslint-disable-line no-magic-numbers
4867
+
4868
+ // Walk the active element chain through shadow roots
4869
+ const actives = this._getActiveElements();
4870
+
4871
+ let idx = focusables.findIndex((el) => actives.includes(el));
4872
+
4873
+ if (idx === -1) { // eslint-disable-line no-magic-numbers
4874
+ // Focus is not on a known element — move to first/last
4875
+ idx = direction === 1 ? -1 : focusables.length; // eslint-disable-line no-magic-numbers
4876
+ }
4877
+
4878
+ // Try each element in order, skipping any that can't receive focus
4879
+ // (e.g. hidden elements, elements in collapsed sections)
4880
+ for (let index = 0; index < focusables.length; index++) { // eslint-disable-line no-plusplus
4881
+ let nextIdx = idx + direction;
4882
+
4883
+ // Wrap around
4884
+ if (nextIdx < 0) {
4885
+ nextIdx = focusables.length - 1;
4886
+ } else if (nextIdx >= focusables.length) {
4887
+ nextIdx = 0;
4888
+ }
4889
+
4890
+ focusables[nextIdx].focus();
4891
+
4892
+ // Verify focus actually moved to the target
4893
+ const newActives = this._getActiveElements();
4894
+
4895
+ if (newActives.includes(focusables[nextIdx])) {
4896
+ return;
4897
+ }
4898
+
4899
+ // Focus didn't stick — skip this element and try the next
4900
+ idx = nextIdx;
4901
+ }
4902
+ };
4903
+ this.addEventListener('keydown', this._bibTabHandler);
4904
+
4905
+ // Move initial focus into the bib content, matching FocusTrap behavior
4906
+ requestAnimationFrame(() => {
4907
+ const focusables = getFocusableElements(this.bibContent);
4908
+ if (focusables.length) {
4909
+ focusables[0].focus();
4910
+ }
4911
+ });
4912
+ } else {
4913
+ // Normal desktop: use FocusTrap on the bib element
4914
+ this.focusTrap = new FocusTrap(this.bibContent);
4915
+ this.focusTrap.focusFirstElement();
4916
+ }
4773
4917
  }
4774
4918
  // Fullscreen: showModal() provides native focus trapping
4919
+ }
4920
+ }
4921
+
4922
+ /**
4923
+ * Returns the chain of active (focused) elements through shadow roots.
4924
+ * @private
4925
+ * @returns {Array<HTMLElement>}
4926
+ */
4927
+ _getActiveElements() {
4928
+ let { activeElement } = document;
4929
+ const actives = [activeElement];
4930
+
4931
+ while (activeElement?.shadowRoot?.activeElement) {
4932
+ activeElement = activeElement.shadowRoot.activeElement;
4933
+ actives.push(activeElement);
4934
+ }
4935
+
4936
+ return actives;
4937
+ }
4938
+
4939
+ /**
4940
+ * Sets `inert` on sibling elements of the dropdown's top-level host
4941
+ * so that content outside the dropdown is not interactive while the modal is open.
4942
+ * Walks up through shadow DOM boundaries to find the outermost host element
4943
+ * in the light DOM, then sets `inert` on siblings at each ancestor level
4944
+ * to ensure all page content outside the host subtree is inert.
4945
+ * @private
4946
+ */
4947
+ _setPageInert() {
4948
+ if (this._inertSiblings) {
4775
4949
  return;
4776
4950
  }
4777
4951
 
4778
- if (this.focusTrap) {
4779
- this.focusTrap.disconnect();
4780
- this.focusTrap = undefined;
4952
+ this._inertSiblings = [];
4953
+
4954
+ // Walk up through shadow DOM boundaries to find the topmost host
4955
+ // element in the light DOM. For example, if this dropdown is inside
4956
+ // auro-datepicker's shadow DOM, we walk up to the datepicker element
4957
+ // so we set inert on its siblings — not on the datepicker itself.
4958
+ let host = this;
4959
+ while (host.getRootNode() instanceof ShadowRoot) {
4960
+ host = host.getRootNode().host;
4961
+ }
4962
+
4963
+ // Walk up the ancestor chain, inerting siblings at each level
4964
+ // to ensure the entire page outside the host subtree is inert.
4965
+ let current = host;
4966
+ while (current.parentElement) {
4967
+ const parent = current.parentElement;
4968
+ for (const sibling of parent.children) {
4969
+ if (sibling !== current) {
4970
+ this._inertSiblings.push({ element: sibling, wasInert: sibling.inert });
4971
+ sibling.inert = true;
4972
+ }
4973
+ }
4974
+ current = parent;
4975
+ }
4976
+ }
4977
+
4978
+ /**
4979
+ * Restores `inert` state on siblings that were tracked by `_setPageInert`.
4980
+ * Preserves the previous inert state so externally-inerted elements are
4981
+ * not inadvertently re-enabled.
4982
+ * @private
4983
+ */
4984
+ _clearPageInert() {
4985
+ if (this._inertSiblings) {
4986
+ for (const entry of this._inertSiblings) {
4987
+ entry.element.inert = entry.wasInert;
4988
+ }
4989
+ this._inertSiblings = undefined;
4781
4990
  }
4782
4991
  }
4783
4992
 
@@ -5016,6 +5225,7 @@ class AuroDropdown extends AuroElement {
5016
5225
  shape="${this.shape}"
5017
5226
  ?data-show="${this.isPopoverVisible}"
5018
5227
  ?isfullscreen="${this.isBibFullscreen}"
5228
+ ?desktopmodal="${this.desktopModal}"
5019
5229
  .dialogLabel="${this.bibDialogLabel}"
5020
5230
  ${n$2(this.bibElement)}
5021
5231
  >
@@ -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) -->
@@ -1 +1 @@
1
- ["accessibility.md","api.md","customize.md","design.md","getting-started.md","index.md","keyboard-behavior.md","voiceover.md","readme.md"]
1
+ ["accessibility.md","api.md","customize.md","design.md","getting-started.md","index.md","keyboard-behavior.md","voiceover.md","why-dropdown.md","readme.md"]
@@ -0,0 +1,57 @@
1
+ <!--
2
+ Copyright (c) Alaska Air. All right reserved. Licensed under the Apache-2.0 license
3
+ See LICENSE in the project root for license information.
4
+
5
+ HTML in this document is standardized and NOT to be edited.
6
+ All demo code should be added/edited in ./demo/why-dropdown.md
7
+
8
+ With the exception of adding custom elements if needed for the demo.
9
+
10
+ ----------------------- DO NOT EDIT -----------------------------
11
+
12
+ -->
13
+
14
+ <!DOCTYPE html>
15
+ <html lang="en">
16
+ <head>
17
+ <meta charset="UTF-8" />
18
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
19
+ <title>Auro Web Component Demo | auro-dropdown | Why auro-dropdown</title>
20
+
21
+ <!-- highlight.js Stylesheet -->
22
+ <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/styles/github.min.css"/>
23
+
24
+ <!-- Legacy reference is still needed to support auro-dropdown's use of legacy token values at this time -->
25
+ <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/@aurodesignsystem/design-tokens@latest/dist/legacy/auro-classic/CSSCustomProperties.css"/>
26
+
27
+ <!-- Design Token Alaska Theme -->
28
+ <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/@aurodesignsystem/design-tokens@latest/dist/themes/alaska/CSSCustomProperties--alaska.min.css"/>
29
+
30
+ <!-- Webcore Stylesheet Alaska Theme -->
31
+ <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/@aurodesignsystem/webcorestylesheets@latest/dist/bundled/themes/alaska.global.min.css" />
32
+
33
+ <!-- Demo Specific Styles -->
34
+ <link rel="stylesheet" type="text/css" href="./styles.min.css" />
35
+ <style>
36
+ table {
37
+ --ds-color-container-secondary-default: transparent;
38
+ }
39
+
40
+ tr:not(:last-of-type) {
41
+ border-bottom: 1px solid var(--ds-color-border-tertiary-default);
42
+ }
43
+ </style>
44
+ </head>
45
+ <body class="auro-markdown">
46
+ <main></main>
47
+
48
+ <script type="module">
49
+ import { renderPage } from './demo-support.min.js';
50
+ await renderPage('./why-dropdown.md');
51
+ </script>
52
+
53
+ <!-- If additional elements are needed for the demo, add them here. -->
54
+ <script src="https://cdn.jsdelivr.net/npm/@aurodesignsystem/auro-header@latest/+esm" type="module"></script>
55
+ <script src="https://cdn.jsdelivr.net/npm/@aurodesignsystem/auro-hyperlink@latest/+esm" type="module"></script>
56
+ </body>
57
+ </html>
@@ -0,0 +1,97 @@
1
+ <auro-header level="1" id="overview">Why auro-dropdown?</auro-header>
2
+ <p>Native HTML has no dedicated dropdown/popover primitive that combines trigger management, content positioning, focus trapping, and responsive behavior. Building a dropdown from <code>&lt;details&gt;</code>/<code>&lt;summary&gt;</code> or custom <code>&lt;div&gt;</code> toggles requires significant work for accessibility and positioning. <code>auro-dropdown</code> provides a production-ready solution.</p>
3
+ <auro-header level="2" id="accessibility">Accessibility</auro-header>
4
+ <p>Custom dropdown implementations often break keyboard navigation, fail to trap focus, or leave background content interactive when the dropdown is open.</p>
5
+ <p><code>auro-dropdown</code> provides:</p>
6
+ <ul>
7
+ <li><strong>Focus trapping</strong> — In desktop modal mode, Tab and Shift+Tab cycle within the dropdown content. Background content is made inert. The implementation handles focus across shadow DOM boundaries.</li>
8
+ <li><strong>Native dialog semantics</strong> — On mobile, the dropdown opens via <code>showModal()</code>, which provides browser-native focus trapping and background inert behavior.</li>
9
+ <li><strong>ARIA attributes</strong> — The trigger carries <code>aria-expanded</code>, <code>aria-controls</code>, and a configurable <code>role</code> (default: <code>button</code>). The dropdown bib has an accessible dialog label.</li>
10
+ <li><strong>Focus delegation</strong> — <code>delegatesFocus: true</code> ensures clicks on the trigger container route focus to the correct interactive element.</li>
11
+ <li><strong>Escape to close</strong> — The dialog's cancel event is handled to close the dropdown on Escape.</li>
12
+ </ul>
13
+ <auro-header level="2" id="positioning">Positioning</auro-header>
14
+ <p>Positioning a dropdown relative to its trigger — accounting for viewport edges, scroll containers, and dynamic content — is a complex problem that native HTML does not solve.</p>
15
+ <p><code>auro-dropdown</code> integrates Floating UI with:</p>
16
+ <ul>
17
+ <li>Configurable <code>placement</code> (12 positions: top/bottom/left/right with start/center/end)</li>
18
+ <li><code>offset</code> control for gap between trigger and content</li>
19
+ <li><code>noFlip</code> to prevent automatic repositioning</li>
20
+ <li><code>shift</code> to slide the dropdown along the edge to avoid clipping</li>
21
+ <li><code>autoPlacement</code> to let the library choose the best position</li>
22
+ <li><code>matchWidth</code> to size the dropdown to the trigger width</li>
23
+ <li>Layout containment escape so the dropdown is not clipped by ancestor <code>overflow</code> or <code>&lt;dialog&gt;</code> elements</li>
24
+ </ul>
25
+ <auro-header level="2" id="responsiveBehavior">Responsive behavior</auro-header>
26
+ <p>Native disclosure elements have no concept of viewport-aware presentation.</p>
27
+ <p><code>auro-dropdown</code> adapts to the viewport:</p>
28
+ <ul>
29
+ <li><strong>Desktop</strong> — Content appears as a positioned panel with optional modal behavior</li>
30
+ <li><strong>Mobile</strong> — Content opens as a fullscreen dialog below a configurable breakpoint (<code>fullscreenBreakpoint</code>: xs, sm, md, lg, xl, or disabled)</li>
31
+ <li><strong>Desktop modal</strong> — <code>desktopModal</code> makes background siblings inert, traps focus within the dropdown, and prevents interaction with page content — all without going fullscreen</li>
32
+ </ul>
33
+ <auro-header level="2" id="contentagnostic">Content-agnostic</auro-header>
34
+ <p>Unlike <code>&lt;select&gt;</code>, which only accepts <code>&lt;option&gt;</code> elements, <code>auro-dropdown</code> accepts any HTML content in its default slot. This makes it the foundation for composed components like <code>auro-select</code>, <code>auro-combobox</code>, and <code>auro-counter-group</code>.</p>
35
+ <auro-header level="2" id="triggerFlexibility">Trigger flexibility</auro-header>
36
+ <p>The <code>trigger</code> slot accepts any content — plain text, buttons, inputs, or complex custom elements. The component detects whether the trigger content is focusable and adjusts tabindex and ARIA attributes accordingly.</p>
37
+ <auro-header level="2" id="designSystemIntegration">Design system integration</auro-header>
38
+ <p><code>auro-dropdown</code> is built with the Auro Design System:</p>
39
+ <ul>
40
+ <li>Three layout options: classic, emphasized, snowflake</li>
41
+ <li>Light and dark theme support (<code>appearance</code>)</li>
42
+ <li>Chevron indicator for expand/collapse state</li>
43
+ <li>CSS <code>::part()</code> selectors (<code>trigger</code>, <code>chevron</code>, <code>size</code>, <code>helpText</code>)</li>
44
+ <li>Help text slot with error message support</li>
45
+ </ul>
46
+ <auro-header level="2" id="summary">Summary</auro-header>
47
+ <table>
48
+ <thead>
49
+ <tr>
50
+ <th>Capability</th>
51
+ <th><code>&lt;details&gt;</code> / custom div</th>
52
+ <th><code>auro-dropdown</code></th>
53
+ </tr>
54
+ </thead>
55
+ <tbody>
56
+ <tr>
57
+ <td>Viewport-aware positioning</td>
58
+ <td>No</td>
59
+ <td>Floating UI with flip/shift/auto</td>
60
+ </tr>
61
+ <tr>
62
+ <td>Focus trapping</td>
63
+ <td>No</td>
64
+ <td>Desktop modal + fullscreen dialog</td>
65
+ </tr>
66
+ <tr>
67
+ <td>Background inert</td>
68
+ <td>No</td>
69
+ <td>Automatic in modal modes</td>
70
+ </tr>
71
+ <tr>
72
+ <td>Keyboard close (Escape)</td>
73
+ <td>No</td>
74
+ <td>Built-in</td>
75
+ </tr>
76
+ <tr>
77
+ <td>Mobile fullscreen</td>
78
+ <td>No</td>
79
+ <td>Automatic at breakpoint</td>
80
+ </tr>
81
+ <tr>
82
+ <td>Any HTML content</td>
83
+ <td>Yes</td>
84
+ <td>Yes</td>
85
+ </tr>
86
+ <tr>
87
+ <td>Trigger detection</td>
88
+ <td>No</td>
89
+ <td>Auto-detects focusable content</td>
90
+ </tr>
91
+ <tr>
92
+ <td>Theming</td>
93
+ <td>No</td>
94
+ <td>Three layouts + appearance modes</td>
95
+ </tr>
96
+ </tbody>
97
+ </table>
@@ -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,29 @@ 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 siblings at each ancestor level
424
+ * to ensure all page content outside the host subtree is inert.
425
+ * @private
426
+ */
427
+ private _setPageInert;
428
+ _inertSiblings: any[] | undefined;
429
+ /**
430
+ * Restores `inert` state on siblings that were tracked by `_setPageInert`.
431
+ * Preserves the previous inert state so externally-inerted elements are
432
+ * not inadvertently re-enabled.
433
+ * @private
434
+ */
435
+ private _clearPageInert;
404
436
  /**
405
437
  * Function to support @focusout event.
406
438
  * @private