@aurodesignsystem-dev/auro-formkit 0.0.0-pr624.16 → 0.0.0-pr624.18

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 (32) hide show
  1. package/components/bibtemplate/dist/buttonVersion.d.ts +1 -1
  2. package/components/bibtemplate/dist/index.js +61 -10
  3. package/components/bibtemplate/dist/registered.js +61 -10
  4. package/components/combobox/demo/api.min.js +134 -20
  5. package/components/combobox/demo/index.min.js +134 -20
  6. package/components/combobox/dist/index.js +122 -20
  7. package/components/combobox/dist/registered.js +122 -20
  8. package/components/counter/demo/api.min.js +478 -93
  9. package/components/counter/demo/index.min.js +478 -93
  10. package/components/counter/dist/auro-counter-group.d.ts +16 -8
  11. package/components/counter/dist/auro-counter.d.ts +6 -0
  12. package/components/counter/dist/index.js +478 -93
  13. package/components/counter/dist/registered.js +478 -93
  14. package/components/datepicker/demo/api.min.js +181 -28
  15. package/components/datepicker/demo/index.min.js +181 -28
  16. package/components/datepicker/dist/index.js +181 -28
  17. package/components/datepicker/dist/registered.js +181 -28
  18. package/components/input/demo/api.min.js +60 -9
  19. package/components/input/demo/index.min.js +60 -9
  20. package/components/input/dist/index.js +60 -9
  21. package/components/input/dist/registered.js +60 -9
  22. package/components/menu/demo/api.min.js +12 -0
  23. package/components/menu/demo/index.min.js +12 -0
  24. package/components/menu/dist/auro-menu.d.ts +6 -0
  25. package/components/menu/dist/index.js +12 -0
  26. package/components/menu/dist/registered.js +12 -0
  27. package/components/select/demo/api.min.js +94 -17
  28. package/components/select/demo/index.min.js +94 -17
  29. package/components/select/dist/auro-select.d.ts +7 -0
  30. package/components/select/dist/index.js +82 -17
  31. package/components/select/dist/registered.js +82 -17
  32. package/package.json +3 -3
@@ -391,7 +391,6 @@ let AuroElement$5 = class AuroElement extends LitElement {
391
391
  * @private
392
392
  */
393
393
  wrapper: {
394
- type: HTMLElement,
395
394
  attribute: false,
396
395
  reflect: false
397
396
  }
@@ -810,7 +809,7 @@ let AuroLoader$1 = class AuroLoader extends LitElement {
810
809
 
811
810
  var loaderVersion$1 = '5.0.0';
812
811
 
813
- /* eslint-disable max-lines, curly */
812
+ /* eslint-disable max-lines, curly, jsdoc/no-undefined-types */
814
813
  // Copyright (c) Alaska Air. All right reserved. Licensed under the Apache-2.0 license
815
814
  // See LICENSE in the project root for license information.
816
815
 
@@ -877,6 +876,21 @@ let AuroButton$1 = class AuroButton extends AuroElement$5 {
877
876
  * @private
878
877
  */
879
878
  this.loaderTag = versioning.generateTag('auro-loader', loaderVersion$1, AuroLoader$1);
879
+
880
+ /**
881
+ * @private
882
+ */
883
+ this.buttonHref = undefined;
884
+
885
+ /**
886
+ * @private
887
+ */
888
+ this.buttonTarget = undefined;
889
+
890
+ /**
891
+ * @private
892
+ */
893
+ this.buttonRel = undefined;
880
894
  }
881
895
 
882
896
  static get styles() {
@@ -943,13 +957,23 @@ let AuroButton$1 = class AuroButton extends AuroElement$5 {
943
957
  },
944
958
 
945
959
  /**
946
- * Populates `tabIndex` to define the focusable sequence in keyboard navigation.
960
+ * Populates `tabindex` to define the focusable sequence in keyboard navigation.
947
961
  */
948
962
  tIndex: {
949
963
  type: String,
950
964
  reflect: true
951
965
  },
952
966
 
967
+ /**
968
+ * Populates `tabindex` to define the focusable sequence in keyboard navigation.
969
+ * Must be used with "." to ensure the host element does not retain a reference to the `tabindex` attribute.
970
+ * Example: `<auro-button .tabindex="${this.disabled ? '-1' : '0'}"></auro-button>`
971
+ */
972
+ tabindex: {
973
+ type: String,
974
+ reflect: false
975
+ },
976
+
953
977
  /**
954
978
  * Sets title attribute. The information is most often shown as a tooltip text when the mouse moves over the element.
955
979
  */
@@ -982,6 +1006,27 @@ let AuroButton$1 = class AuroButton extends AuroElement$5 {
982
1006
  type: String,
983
1007
  reflect: true
984
1008
  },
1009
+
1010
+ /**
1011
+ * @private
1012
+ */
1013
+ buttonHref: {
1014
+ type: String,
1015
+ },
1016
+
1017
+ /**
1018
+ * @private
1019
+ */
1020
+ buttonTarget: {
1021
+ type: String,
1022
+ },
1023
+
1024
+ /**
1025
+ * @private
1026
+ */
1027
+ buttonRel: {
1028
+ type: String,
1029
+ },
985
1030
  };
986
1031
  }
987
1032
 
@@ -1072,14 +1117,17 @@ let AuroButton$1 = class AuroButton extends AuroElement$5 {
1072
1117
  loading: this.loading,
1073
1118
  };
1074
1119
 
1120
+ const tag = this.buttonHref ? literal`a` : literal`button`;
1121
+ const part = this.buttonHref ? 'link' : 'button';
1122
+
1075
1123
  return html$1`
1076
- <button
1077
- part="button"
1124
+ <${tag}
1125
+ part="${part}"
1078
1126
  aria-label="${ifDefined(this.loading ? this.loadingText : this.currentAriaLabel || undefined)}"
1079
1127
  aria-labelledby="${ifDefined(this.loading ? undefined : this.currentAriaLabelledBy || undefined)}"
1080
- tabIndex="${ifDefined(this.tIndex)}"
1128
+ tabindex="${ifDefined(this.tIndex || this.tabindex)}"
1081
1129
  ?autofocus="${this.autofocus}"
1082
- class="${classMap(classes)}"
1130
+ class=${classMap(classes)}
1083
1131
  ?disabled="${this.disabled || this.loading}"
1084
1132
  ?onDark="${this.onDark}"
1085
1133
  title="${ifDefined(this.title ? this.title : undefined)}"
@@ -1088,6 +1136,9 @@ let AuroButton$1 = class AuroButton extends AuroElement$5 {
1088
1136
  variant="${ifDefined(this.variant ? this.variant : undefined)}"
1089
1137
  .value="${ifDefined(this.value ? this.value : undefined)}"
1090
1138
  @click="${this.type === 'submit' ? this.surfaceSubmitEvent : undefined}"
1139
+ href="${ifDefined(this.buttonHref || undefined)}"
1140
+ target="${ifDefined(this.buttonTarget || undefined)}"
1141
+ rel="${ifDefined(this.buttonRel || undefined)}"
1091
1142
  >
1092
1143
  ${ifDefined(this.loading ? html$1`<${this.loaderTag} pulse part="loader"></${this.loaderTag}>` : undefined)}
1093
1144
 
@@ -1096,12 +1147,12 @@ let AuroButton$1 = class AuroButton extends AuroElement$5 {
1096
1147
  <slot></slot>
1097
1148
  </span>
1098
1149
  </span>
1099
- </button>
1150
+ </${tag}>
1100
1151
  `;
1101
1152
  }
1102
1153
 
1103
1154
  /**
1104
- * Renders the layout of the button
1155
+ * Renders the layout of the button.
1105
1156
  * @returns {TemplateResult}
1106
1157
  * @private
1107
1158
  */
@@ -2615,6 +2666,18 @@ class AuroCounter extends LitElement {
2615
2666
 
2616
2667
  firstUpdated() {
2617
2668
  this.initValue();
2669
+ this.setTagAttribute("auro-counter");
2670
+ }
2671
+
2672
+ /**
2673
+ * Sets an attribute that matches the default tag name if the tag name is not the default.
2674
+ * @param {string} tagName - The tag name to set as an attribute.
2675
+ * @private
2676
+ */
2677
+ setTagAttribute(tagName) {
2678
+ if (this.tagName.toLowerCase() !== tagName) {
2679
+ this.setAttribute(tagName, true);
2680
+ }
2618
2681
  }
2619
2682
 
2620
2683
  /**
@@ -2655,14 +2718,24 @@ class AuroCounter extends LitElement {
2655
2718
  <label id="counter-label" class="label"><slot @slotchange="${this.onDefaultSlotChange}" ></slot></label>
2656
2719
  <slot id="counter-description" name="description"></slot>
2657
2720
  </div>
2658
- <div part="counterControl" aria-labelledby="counter-label" aria-describedby="counter-description" tabindex="${this.disabled ? '-1' : '0'}" role="spinbutton" aria-valuemin="${this.min}" aria-valuemax="${this.max}" aria-valuenow="${this.value}">
2721
+ <div
2722
+ part="counterControl"
2723
+ aria-labelledby="counter-label"
2724
+ aria-describedby="counter-description"
2725
+ tabindex="${this.disabled ? '-1' : '0'}"
2726
+ role="spinbutton"
2727
+ aria-valuemin="${this.min}"
2728
+ aria-valuemax="${this.max}"
2729
+ aria-valuenow="${this.value}"
2730
+ aria-valuetext="${this.value !== undefined ? this.value : this.min}"
2731
+ >
2659
2732
  <auro-counter-button
2660
- aria-hidden="true"
2661
- tindex="-1"
2662
- part="controlMinus"
2663
- @click="${() => this.decrement()}"
2664
- ?onDark="${this.onDark}"
2665
- ?disabled="${this.disabled || this.disableMin || this.isIncrementDisabled(this.min)}"
2733
+ aria-hidden="true"
2734
+ .tabindex="${'-1'}"
2735
+ part="controlMinus"
2736
+ @click="${() => this.decrement()}"
2737
+ ?onDark="${this.onDark}"
2738
+ ?disabled="${this.disabled || this.disableMin || this.isIncrementDisabled(this.min)}"
2666
2739
  >
2667
2740
  <${this.iconTag} class="controlIcon" customSvg> ${IconUtil.generateSvgHtml(minusIcon)} </${this.iconTag}>
2668
2741
  </auro-counter-button>
@@ -2672,12 +2745,12 @@ class AuroCounter extends LitElement {
2672
2745
  </div>
2673
2746
 
2674
2747
  <auro-counter-button
2675
- aria-hidden="true"
2676
- tindex="-1"
2677
- part="controlPlus"
2678
- @click="${() => this.increment()}"
2679
- ?onDark="${this.onDark}"
2680
- ?disabled="${this.disabled || this.disableMax || this.isIncrementDisabled(this.max)}"
2748
+ aria-hidden="true"
2749
+ .tabindex="${'-1'}"
2750
+ part="controlPlus"
2751
+ @click="${() => this.increment()}"
2752
+ ?onDark="${this.onDark}"
2753
+ ?disabled="${this.disabled || this.disableMax || this.isIncrementDisabled(this.max)}"
2681
2754
  >
2682
2755
  <${this.iconTag} class="controlIcon" customSvg> ${IconUtil.generateSvgHtml(plusIcon)} </${this.iconTag}>
2683
2756
  </auro-counter-button>
@@ -7304,7 +7377,6 @@ let AuroElement$1 = class AuroElement extends LitElement {
7304
7377
  * @private
7305
7378
  */
7306
7379
  wrapper: {
7307
- type: HTMLElement,
7308
7380
  attribute: false,
7309
7381
  reflect: false
7310
7382
  }
@@ -7613,7 +7685,7 @@ class AuroLoader extends LitElement {
7613
7685
 
7614
7686
  var loaderVersion = '5.0.0';
7615
7687
 
7616
- /* eslint-disable max-lines, curly */
7688
+ /* eslint-disable max-lines, curly, jsdoc/no-undefined-types */
7617
7689
  // Copyright (c) Alaska Air. All right reserved. Licensed under the Apache-2.0 license
7618
7690
  // See LICENSE in the project root for license information.
7619
7691
 
@@ -7680,6 +7752,21 @@ class AuroButton extends AuroElement$1 {
7680
7752
  * @private
7681
7753
  */
7682
7754
  this.loaderTag = versioning.generateTag('auro-loader', loaderVersion, AuroLoader);
7755
+
7756
+ /**
7757
+ * @private
7758
+ */
7759
+ this.buttonHref = undefined;
7760
+
7761
+ /**
7762
+ * @private
7763
+ */
7764
+ this.buttonTarget = undefined;
7765
+
7766
+ /**
7767
+ * @private
7768
+ */
7769
+ this.buttonRel = undefined;
7683
7770
  }
7684
7771
 
7685
7772
  static get styles() {
@@ -7746,13 +7833,23 @@ class AuroButton extends AuroElement$1 {
7746
7833
  },
7747
7834
 
7748
7835
  /**
7749
- * Populates `tabIndex` to define the focusable sequence in keyboard navigation.
7836
+ * Populates `tabindex` to define the focusable sequence in keyboard navigation.
7750
7837
  */
7751
7838
  tIndex: {
7752
7839
  type: String,
7753
7840
  reflect: true
7754
7841
  },
7755
7842
 
7843
+ /**
7844
+ * Populates `tabindex` to define the focusable sequence in keyboard navigation.
7845
+ * Must be used with "." to ensure the host element does not retain a reference to the `tabindex` attribute.
7846
+ * Example: `<auro-button .tabindex="${this.disabled ? '-1' : '0'}"></auro-button>`
7847
+ */
7848
+ tabindex: {
7849
+ type: String,
7850
+ reflect: false
7851
+ },
7852
+
7756
7853
  /**
7757
7854
  * Sets title attribute. The information is most often shown as a tooltip text when the mouse moves over the element.
7758
7855
  */
@@ -7785,6 +7882,27 @@ class AuroButton extends AuroElement$1 {
7785
7882
  type: String,
7786
7883
  reflect: true
7787
7884
  },
7885
+
7886
+ /**
7887
+ * @private
7888
+ */
7889
+ buttonHref: {
7890
+ type: String,
7891
+ },
7892
+
7893
+ /**
7894
+ * @private
7895
+ */
7896
+ buttonTarget: {
7897
+ type: String,
7898
+ },
7899
+
7900
+ /**
7901
+ * @private
7902
+ */
7903
+ buttonRel: {
7904
+ type: String,
7905
+ },
7788
7906
  };
7789
7907
  }
7790
7908
 
@@ -7875,14 +7993,17 @@ class AuroButton extends AuroElement$1 {
7875
7993
  loading: this.loading,
7876
7994
  };
7877
7995
 
7996
+ const tag = this.buttonHref ? literal`a` : literal`button`;
7997
+ const part = this.buttonHref ? 'link' : 'button';
7998
+
7878
7999
  return html$1`
7879
- <button
7880
- part="button"
8000
+ <${tag}
8001
+ part="${part}"
7881
8002
  aria-label="${ifDefined(this.loading ? this.loadingText : this.currentAriaLabel || undefined)}"
7882
8003
  aria-labelledby="${ifDefined(this.loading ? undefined : this.currentAriaLabelledBy || undefined)}"
7883
- tabIndex="${ifDefined(this.tIndex)}"
8004
+ tabindex="${ifDefined(this.tIndex || this.tabindex)}"
7884
8005
  ?autofocus="${this.autofocus}"
7885
- class="${classMap(classes)}"
8006
+ class=${classMap(classes)}
7886
8007
  ?disabled="${this.disabled || this.loading}"
7887
8008
  ?onDark="${this.onDark}"
7888
8009
  title="${ifDefined(this.title ? this.title : undefined)}"
@@ -7891,6 +8012,9 @@ class AuroButton extends AuroElement$1 {
7891
8012
  variant="${ifDefined(this.variant ? this.variant : undefined)}"
7892
8013
  .value="${ifDefined(this.value ? this.value : undefined)}"
7893
8014
  @click="${this.type === 'submit' ? this.surfaceSubmitEvent : undefined}"
8015
+ href="${ifDefined(this.buttonHref || undefined)}"
8016
+ target="${ifDefined(this.buttonTarget || undefined)}"
8017
+ rel="${ifDefined(this.buttonRel || undefined)}"
7894
8018
  >
7895
8019
  ${ifDefined(this.loading ? html$1`<${this.loaderTag} pulse part="loader"></${this.loaderTag}>` : undefined)}
7896
8020
 
@@ -7899,12 +8023,12 @@ class AuroButton extends AuroElement$1 {
7899
8023
  <slot></slot>
7900
8024
  </span>
7901
8025
  </span>
7902
- </button>
8026
+ </${tag}>
7903
8027
  `;
7904
8028
  }
7905
8029
 
7906
8030
  /**
7907
- * Renders the layout of the button
8031
+ * Renders the layout of the button.
7908
8032
  * @returns {TemplateResult}
7909
8033
  * @private
7910
8034
  */
@@ -7913,7 +8037,7 @@ class AuroButton extends AuroElement$1 {
7913
8037
  }
7914
8038
  }
7915
8039
 
7916
- var buttonVersion = '11.0.0';
8040
+ var buttonVersion = '11.2.1';
7917
8041
 
7918
8042
  // Copyright (c) 2020 Alaska Airlines. All right reserved. Licensed under the Apache-2.0 license
7919
8043
  // See LICENSE in the project root for license information.
@@ -9026,7 +9150,266 @@ class AuroElement extends LitElement {
9026
9150
  }
9027
9151
  }
9028
9152
 
9029
- /* eslint-disable lit/no-invalid-html, lit/binding-positions, max-lines, prefer-destructuring, no-underscore-dangle, arrow-parens, no-confusing-arrow, curly */
9153
+ // Selectors for focusable elements
9154
+ const FOCUSABLE_SELECTORS = [
9155
+ 'a[href]',
9156
+ 'button:not([disabled])',
9157
+ 'textarea:not([disabled])',
9158
+ 'input:not([disabled])',
9159
+ 'select:not([disabled])',
9160
+ '[role="tab"]:not([disabled])',
9161
+ '[role="link"]:not([disabled])',
9162
+ '[role="button"]:not([disabled])',
9163
+ '[tabindex]:not([tabindex="-1"])',
9164
+ '[contenteditable]:not([contenteditable="false"])'
9165
+ ];
9166
+
9167
+ // List of custom components that are known to be focusable
9168
+ const FOCUSABLE_COMPONENTS = [
9169
+ 'auro-checkbox',
9170
+ 'auro-radio',
9171
+ 'auro-dropdown',
9172
+ 'auro-button',
9173
+ 'auro-combobox',
9174
+ 'auro-input',
9175
+ 'auro-counter',
9176
+ 'auro-menu',
9177
+ 'auro-select',
9178
+ 'auro-datepicker',
9179
+ 'auro-hyperlink',
9180
+ 'auro-accordion',
9181
+ ];
9182
+
9183
+ /**
9184
+ * Determines if a given element is a custom focusable component.
9185
+ * Returns true if the element matches a known focusable component and is not disabled.
9186
+ *
9187
+ * @param {HTMLElement} element The element to check for focusability.
9188
+ * @returns {boolean} True if the element is a focusable custom component, false otherwise.
9189
+ */
9190
+ function isFocusableComponent(element) {
9191
+ const componentName = element.tagName.toLowerCase();
9192
+
9193
+ // Guard Clause: Element is a focusable component
9194
+ if (!FOCUSABLE_COMPONENTS.some((name) => element.hasAttribute(name) || componentName === name)) return false;
9195
+
9196
+ // Guard Clause: Element is not disabled
9197
+ if (element.hasAttribute('disabled')) return false;
9198
+
9199
+ // Guard Clause: The element is a hyperlink and has no href attribute
9200
+ if (componentName.match("hyperlink") && !element.hasAttribute('href')) return false;
9201
+
9202
+ // If all guard clauses pass, the element is a focusable component
9203
+ return true;
9204
+ }
9205
+
9206
+ /**
9207
+ * Retrieves all focusable elements within the container in DOM order, including those in shadow DOM and slots.
9208
+ * Returns a unique, ordered array of elements that can receive focus.
9209
+ *
9210
+ * @param {HTMLElement} container The container to search within
9211
+ * @returns {Array<HTMLElement>} An array of focusable elements within the container.
9212
+ */
9213
+ function getFocusableElements(container) {
9214
+ // Get elements in DOM order by walking the tree
9215
+ const orderedFocusableElements = [];
9216
+
9217
+ // Define a recursive function to collect focusable elements in DOM order
9218
+ const collectFocusableElements = (root) => {
9219
+ // Check if current element is focusable
9220
+ if (root.nodeType === Node.ELEMENT_NODE) {
9221
+ // Check if this is a custom component that is focusable
9222
+ const isComponentFocusable = isFocusableComponent(root);
9223
+
9224
+ if (isComponentFocusable) {
9225
+ // Add the component itself as a focusable element and don't traverse its shadow DOM
9226
+ orderedFocusableElements.push(root);
9227
+ return; // Skip traversing inside this component
9228
+ }
9229
+
9230
+ // Check if the element itself matches any selector
9231
+ for (const selector of FOCUSABLE_SELECTORS) {
9232
+ if (root.matches?.(selector)) {
9233
+ orderedFocusableElements.push(root);
9234
+ break; // Once we know it's focusable, no need to check other selectors
9235
+ }
9236
+ }
9237
+
9238
+ // Process shadow DOM only for non-Auro components
9239
+ if (root.shadowRoot) {
9240
+ // Process shadow DOM children in order
9241
+ if (root.shadowRoot.children) {
9242
+ Array.from(root.shadowRoot.children).forEach(child => {
9243
+ collectFocusableElements(child);
9244
+ });
9245
+ }
9246
+ }
9247
+
9248
+ // Process slots and their assigned nodes in order
9249
+ if (root.tagName === 'SLOT') {
9250
+ const assignedNodes = root.assignedNodes({ flatten: true });
9251
+ for (const node of assignedNodes) {
9252
+ collectFocusableElements(node);
9253
+ }
9254
+ } else {
9255
+ // Process light DOM children in order
9256
+ if (root.children) {
9257
+ Array.from(root.children).forEach(child => {
9258
+ collectFocusableElements(child);
9259
+ });
9260
+ }
9261
+ }
9262
+ }
9263
+ };
9264
+
9265
+ // Start the traversal from the container
9266
+ collectFocusableElements(container);
9267
+
9268
+ // Remove duplicates that might have been collected through different paths
9269
+ // while preserving order
9270
+ const uniqueElements = [];
9271
+ const seen = new Set();
9272
+
9273
+ for (const element of orderedFocusableElements) {
9274
+ if (!seen.has(element)) {
9275
+ seen.add(element);
9276
+ uniqueElements.push(element);
9277
+ }
9278
+ }
9279
+
9280
+ return uniqueElements;
9281
+ }
9282
+
9283
+ /**
9284
+ * FocusTrap manages keyboard focus within a specified container element, ensuring that focus does not leave the container when tabbing.
9285
+ * It is commonly used for modal dialogs or overlays to improve accessibility by trapping focus within interactive UI components.
9286
+ */
9287
+ class FocusTrap {
9288
+ /**
9289
+ * Creates a new FocusTrap instance for the given container element.
9290
+ * Initializes event listeners and prepares the container for focus management.
9291
+ *
9292
+ * @param {HTMLElement} container The DOM element to trap focus within.
9293
+ * @throws {Error} If the provided container is not a valid HTMLElement.
9294
+ */
9295
+ constructor(container) {
9296
+ if (!container || !(container instanceof HTMLElement)) {
9297
+ throw new Error("FocusTrap requires a valid HTMLElement.");
9298
+ }
9299
+
9300
+ this.container = container;
9301
+ this.tabDirection = 'forward'; // or 'backward'
9302
+
9303
+ this._init();
9304
+ }
9305
+
9306
+ /**
9307
+ * Initializes the focus trap by setting up event listeners and attributes on the container.
9308
+ * Prepares the container for focus management, including support for shadow DOM and inert attributes.
9309
+ *
9310
+ * @private
9311
+ */
9312
+ _init() {
9313
+
9314
+ // Add inert attribute to prevent focusing programmatically as well (if supported)
9315
+ if ('inert' in HTMLElement.prototype) {
9316
+ this.container.inert = false; // Ensure the container isn't inert
9317
+ this.container.setAttribute('data-focus-trap-container', true); // Mark for identification
9318
+ }
9319
+
9320
+ // Track tab direction
9321
+ this.container.addEventListener('keydown', this._onKeydown);
9322
+ }
9323
+
9324
+ /**
9325
+ * Handles keydown events to manage tab navigation within the container.
9326
+ * Ensures that focus wraps around when reaching the first or last focusable element.
9327
+ *
9328
+ * @param {KeyboardEvent} e The keyboard event triggered by user interaction.
9329
+ * @private
9330
+ */
9331
+ _onKeydown = (e) => {
9332
+
9333
+ if (e.key === 'Tab') {
9334
+
9335
+ // Set the tab direction based on the key pressed
9336
+ this.tabDirection = e.shiftKey ? 'backward' : 'forward';
9337
+
9338
+ // Get the active element(s) in the document and shadow root
9339
+ // This will include the active element in the shadow DOM if it exists
9340
+ // Active element may be inside the shadow DOM depending on delegatesFocus, so we need to check both
9341
+ const actives = [
9342
+ document.activeElement,
9343
+ ...document.activeElement.shadowRoot && [document.activeElement.shadowRoot.activeElement] || []
9344
+ ];
9345
+
9346
+ // Update the focusable elements
9347
+ const focusables = this._getFocusableElements();
9348
+
9349
+ // If we're at either end of the focusable elements, wrap around to the other end
9350
+ const focusIndex =
9351
+ (actives.includes(focusables[0]) || actives.includes(this.container)) && this.tabDirection === 'backward'
9352
+ ? focusables.length - 1
9353
+ : actives.includes(focusables[focusables.length - 1]) && this.tabDirection === 'forward'
9354
+ ? 0
9355
+ : null;
9356
+
9357
+ if (focusIndex !== null) {
9358
+ focusables[focusIndex].focus();
9359
+ e.preventDefault(); // Prevent default tab behavior
9360
+ e.stopPropagation(); // Stop the event from bubbling up
9361
+ }
9362
+ }
9363
+ };
9364
+
9365
+ /**
9366
+ * Retrieves all focusable elements within the container in DOM order, including those in shadow DOM and slots.
9367
+ * Returns a unique, ordered array of elements that can receive focus.
9368
+ *
9369
+ * @returns {Array<HTMLElement>} An array of focusable elements within the container.
9370
+ * @private
9371
+ */
9372
+ _getFocusableElements() {
9373
+ // Use the imported utility function to get focusable elements
9374
+ const elements = getFocusableElements(this.container);
9375
+
9376
+ // Filter out any elements with the 'focus-bookend' class
9377
+ return elements;
9378
+ }
9379
+
9380
+ /**
9381
+ * Moves focus to the first focusable element within the container.
9382
+ * Useful for setting initial focus when activating the focus trap.
9383
+ */
9384
+ focusFirstElement() {
9385
+ const focusables = this._getFocusableElements();
9386
+ if (focusables.length) focusables[0].focus();
9387
+ }
9388
+
9389
+ /**
9390
+ * Moves focus to the last focusable element within the container.
9391
+ * Useful for setting focus when deactivating or cycling focus in reverse.
9392
+ */
9393
+ focusLastElement() {
9394
+ const focusables = this._getFocusableElements();
9395
+ if (focusables.length) focusables[focusables.length - 1].focus();
9396
+ }
9397
+
9398
+ /**
9399
+ * Removes event listeners and attributes added by the focus trap.
9400
+ * Call this method to clean up when the focus trap is no longer needed.
9401
+ */
9402
+ disconnect() {
9403
+
9404
+ if (this.container.hasAttribute('data-focus-trap-container')) {
9405
+ this.container.removeAttribute('data-focus-trap-container');
9406
+ }
9407
+
9408
+ this.container.removeEventListener('keydown', this._onKeydown);
9409
+ }
9410
+ }
9411
+
9412
+ /* eslint-disable lit/no-invalid-html, lit/binding-positions, max-lines, prefer-destructuring, no-underscore-dangle, arrow-parens, no-confusing-arrow, curly, no-unused-expressions */
9030
9413
 
9031
9414
 
9032
9415
  /**
@@ -9085,6 +9468,11 @@ class AuroCounterGroup extends AuroElement {
9085
9468
  */
9086
9469
  this.validation = new AuroFormValidation();
9087
9470
 
9471
+ // Bind callback methods since we can't use arrow functions in class properties
9472
+
9473
+ /** @private */
9474
+ this.handleDropdownToggle = this.handleDropdownToggle.bind(this);
9475
+
9088
9476
  /**
9089
9477
  * Generate unique names for dependency components.
9090
9478
  * @private
@@ -9124,6 +9512,8 @@ class AuroCounterGroup extends AuroElement {
9124
9512
  static get properties() {
9125
9513
  return {
9126
9514
 
9515
+ ...super.properties,
9516
+
9127
9517
  /**
9128
9518
  * If declared, bib's position will be automatically calculated where to appear.
9129
9519
  * @default false
@@ -9262,51 +9652,6 @@ class AuroCounterGroup extends AuroElement {
9262
9652
  };
9263
9653
  }
9264
9654
 
9265
- /**
9266
- * Traps keyboard tab interactions within dropdown when open.
9267
- * @private
9268
- * @param {KeyboardEvent} event - The keyboard event.
9269
- * @param {NodeList} counters - The list of counter elements.
9270
- */
9271
- trapKeyboard(event, counters) {
9272
- if (!this.dropdown.isPopoverVisible) {
9273
- return;
9274
- }
9275
-
9276
- event.stopPropagation();
9277
- event.preventDefault();
9278
-
9279
- const firstFocusable = counters[0];
9280
- const lastFocusable = counters[counters.length - 1];
9281
-
9282
- if (event.key === 'Enter') {
9283
- firstFocusable.focus();
9284
- }
9285
-
9286
- if (event.key === 'Escape') {
9287
- this.dropdown.hide();
9288
- }
9289
-
9290
- if (event.key === 'Tab' && this.dropdown && event.target.offsetParent === this.dropdown.bib) {
9291
- this.dropdown.noHideOnThisFocusLoss = true;
9292
-
9293
- const currentIndex = Array.from(counters).indexOf(document.activeElement);
9294
-
9295
- if (event.shiftKey) {
9296
- if (currentIndex === 0) {
9297
- lastFocusable.focus();
9298
- } else {
9299
- counters[currentIndex - 1].focus();
9300
- }
9301
- } else if (currentIndex === counters.length - 1) {
9302
- firstFocusable.focus();
9303
- } else {
9304
- counters[currentIndex + 1].focus();
9305
- }
9306
-
9307
- }
9308
- }
9309
-
9310
9655
  /**
9311
9656
  * Dynamically disables increment/decrement buttons on a counter based on group value.
9312
9657
  * This method checks the total aggregated value against the group's min and max properties.
@@ -9340,6 +9685,52 @@ class AuroCounterGroup extends AuroElement {
9340
9685
  });
9341
9686
  }
9342
9687
 
9688
+ /**
9689
+ * Performs state updates that should happen when the dropdown is toggled.
9690
+ * @returns {void}
9691
+ * @private
9692
+ */
9693
+ handleDropdownToggle() {
9694
+
9695
+ // Check if the dropdown is open
9696
+ const dropdownIsOpen = this.dropdown.isPopoverVisible;
9697
+
9698
+ // Adds and removes the focus trap based on the dropdown state
9699
+ this.updateFocusTrap(dropdownIsOpen);
9700
+
9701
+ // Tasks to perform if the dropdown is closed
9702
+ if (!dropdownIsOpen) {
9703
+
9704
+ // Shift focus to the dropdown trigger
9705
+ this.dropdown.trigger.focus();
9706
+ }
9707
+ }
9708
+
9709
+ /**
9710
+ * Updates the focus trap based on whether the dropdown is open or closed.
9711
+ * If the dropdown is open, it creates a new focus trap and focuses the first element
9712
+ * If the dropdown is closed, it disconnects the focus trap if it exists to prevent memory leaks and disable focus trapping.
9713
+ * @param {boolean} dropdownIsOpen - Indicates whether the dropdown is currently open.
9714
+ * @returns {void}
9715
+ * @private
9716
+ */
9717
+ updateFocusTrap(dropdownIsOpen) {
9718
+
9719
+ // If the dropdown is open, create a focus trap and focus the first element
9720
+ if (dropdownIsOpen) {
9721
+ this.dropdownFocusTrap = new FocusTrap(this.dropdown.bibContent);
9722
+ this.dropdownFocusTrap.focusFirstElement();
9723
+ return;
9724
+ }
9725
+
9726
+ // Guard Clause: Ensure there is a focus trap currently active before continuing
9727
+ if (!this.dropdownFocusTrap) return;
9728
+
9729
+ // If the dropdown is not open, disconnect the focus trap if it exists
9730
+ this.dropdownFocusTrap.disconnect();
9731
+ this.dropdownFocusTrap = undefined;
9732
+ }
9733
+
9343
9734
  /**
9344
9735
  * Configures the dropdown counters by selecting all `auro-counter` elements,
9345
9736
  * appending them to the `auro-counter-wrapper` element within the shadow DOM,
@@ -9348,28 +9739,14 @@ class AuroCounterGroup extends AuroElement {
9348
9739
  */
9349
9740
  configureDropdownCounters() {
9350
9741
  this.dropdown = this.shadowRoot.querySelector(this.dropdownTag._$litStatic$);
9351
- this.dropdown.addEventListener('keydown', (event) => this.trapKeyboard(event, this.counters, 'dropdown'));
9352
- // notify dropdown to reconfigure as the trigger text is updated
9353
9742
  this.dropdown.requestUpdate();
9354
9743
 
9355
- this.addEventListener('auroDropdown-toggled', () => {
9356
- if (!this.dropdown.isPopoverVisible) {
9357
- this.dropdown.focus();
9358
- }
9359
- });
9744
+ this.dropdown.addEventListener("auroDropdown-toggled", this.handleDropdownToggle);
9360
9745
 
9361
9746
  const counterWrapper = this.shadowRoot.querySelector('auro-counter-wrapper');
9362
9747
  const counterSlot = counterWrapper.querySelector('slot');
9363
9748
  this.counters = counterSlot.assignedElements().filter(el => el.tagName.toLowerCase() === 'auro-counter' || el.hasAttribute('auro-counter'));
9364
9749
 
9365
- if (this.keydownHandler) {
9366
- counterWrapper.removeEventListener('keydown', this.keydownHandler);
9367
- }
9368
- this.keydownHandler = (keydownEvent) => {
9369
- this.trapKeyboard(keydownEvent, this.counters);
9370
- };
9371
- counterWrapper.addEventListener('keydown', this.keydownHandler);
9372
-
9373
9750
  this.counters.forEach((counter) => {
9374
9751
  counter.addEventListener("input", () => this.updateValue());
9375
9752
  });
@@ -9511,6 +9888,13 @@ class AuroCounterGroup extends AuroElement {
9511
9888
  this.updateValueText();
9512
9889
  }
9513
9890
 
9891
+ disconnectedCallback() {
9892
+ super.disconnectedCallback();
9893
+
9894
+ // Remove the event listener for dropdown toggling
9895
+ this.removeEventListener("auroDropdown-toggled", this.handleDropdownToggle);
9896
+ }
9897
+
9514
9898
  /**
9515
9899
  * Registers the custom element with the browser.
9516
9900
  * @param {string} [name="auro-counter-group"] - Custom element name to register.
@@ -9529,6 +9913,7 @@ class AuroCounterGroup extends AuroElement {
9529
9913
  renderCounterDropdown() {
9530
9914
  return html$1`
9531
9915
  <${this.dropdownTag}
9916
+ noHideOnThisFocusLoss
9532
9917
  chevron common fluid
9533
9918
  part="dropdown"
9534
9919
  ?autoPlacement="${this.autoPlacement}"