@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
@@ -419,7 +419,6 @@ let AuroElement$5 = class AuroElement extends i$2 {
419
419
  * @private
420
420
  */
421
421
  wrapper: {
422
- type: HTMLElement,
423
422
  attribute: false,
424
423
  reflect: false
425
424
  }
@@ -857,7 +856,7 @@ let AuroLoader$1 = class AuroLoader extends i$2 {
857
856
 
858
857
  var loaderVersion$1 = '5.0.0';
859
858
 
860
- /* eslint-disable max-lines, curly */
859
+ /* eslint-disable max-lines, curly, jsdoc/no-undefined-types */
861
860
  // Copyright (c) Alaska Air. All right reserved. Licensed under the Apache-2.0 license
862
861
  // See LICENSE in the project root for license information.
863
862
 
@@ -924,6 +923,21 @@ let AuroButton$1 = class AuroButton extends AuroElement$5 {
924
923
  * @private
925
924
  */
926
925
  this.loaderTag = versioning.generateTag('auro-loader', loaderVersion$1, AuroLoader$1);
926
+
927
+ /**
928
+ * @private
929
+ */
930
+ this.buttonHref = undefined;
931
+
932
+ /**
933
+ * @private
934
+ */
935
+ this.buttonTarget = undefined;
936
+
937
+ /**
938
+ * @private
939
+ */
940
+ this.buttonRel = undefined;
927
941
  }
928
942
 
929
943
  static get styles() {
@@ -990,13 +1004,23 @@ let AuroButton$1 = class AuroButton extends AuroElement$5 {
990
1004
  },
991
1005
 
992
1006
  /**
993
- * Populates `tabIndex` to define the focusable sequence in keyboard navigation.
1007
+ * Populates `tabindex` to define the focusable sequence in keyboard navigation.
994
1008
  */
995
1009
  tIndex: {
996
1010
  type: String,
997
1011
  reflect: true
998
1012
  },
999
1013
 
1014
+ /**
1015
+ * Populates `tabindex` to define the focusable sequence in keyboard navigation.
1016
+ * Must be used with "." to ensure the host element does not retain a reference to the `tabindex` attribute.
1017
+ * Example: `<auro-button .tabindex="${this.disabled ? '-1' : '0'}"></auro-button>`
1018
+ */
1019
+ tabindex: {
1020
+ type: String,
1021
+ reflect: false
1022
+ },
1023
+
1000
1024
  /**
1001
1025
  * Sets title attribute. The information is most often shown as a tooltip text when the mouse moves over the element.
1002
1026
  */
@@ -1029,6 +1053,27 @@ let AuroButton$1 = class AuroButton extends AuroElement$5 {
1029
1053
  type: String,
1030
1054
  reflect: true
1031
1055
  },
1056
+
1057
+ /**
1058
+ * @private
1059
+ */
1060
+ buttonHref: {
1061
+ type: String,
1062
+ },
1063
+
1064
+ /**
1065
+ * @private
1066
+ */
1067
+ buttonTarget: {
1068
+ type: String,
1069
+ },
1070
+
1071
+ /**
1072
+ * @private
1073
+ */
1074
+ buttonRel: {
1075
+ type: String,
1076
+ },
1032
1077
  };
1033
1078
  }
1034
1079
 
@@ -1119,14 +1164,17 @@ let AuroButton$1 = class AuroButton extends AuroElement$5 {
1119
1164
  loading: this.loading,
1120
1165
  };
1121
1166
 
1167
+ const tag = this.buttonHref ? i$1`a` : i$1`button`;
1168
+ const part = this.buttonHref ? 'link' : 'button';
1169
+
1122
1170
  return u`
1123
- <button
1124
- part="button"
1171
+ <${tag}
1172
+ part="${part}"
1125
1173
  aria-label="${o(this.loading ? this.loadingText : this.currentAriaLabel || undefined)}"
1126
1174
  aria-labelledby="${o(this.loading ? undefined : this.currentAriaLabelledBy || undefined)}"
1127
- tabIndex="${o(this.tIndex)}"
1175
+ tabindex="${o(this.tIndex || this.tabindex)}"
1128
1176
  ?autofocus="${this.autofocus}"
1129
- class="${e(classes)}"
1177
+ class=${e(classes)}
1130
1178
  ?disabled="${this.disabled || this.loading}"
1131
1179
  ?onDark="${this.onDark}"
1132
1180
  title="${o(this.title ? this.title : undefined)}"
@@ -1135,6 +1183,9 @@ let AuroButton$1 = class AuroButton extends AuroElement$5 {
1135
1183
  variant="${o(this.variant ? this.variant : undefined)}"
1136
1184
  .value="${o(this.value ? this.value : undefined)}"
1137
1185
  @click="${this.type === 'submit' ? this.surfaceSubmitEvent : undefined}"
1186
+ href="${o(this.buttonHref || undefined)}"
1187
+ target="${o(this.buttonTarget || undefined)}"
1188
+ rel="${o(this.buttonRel || undefined)}"
1138
1189
  >
1139
1190
  ${o(this.loading ? u`<${this.loaderTag} pulse part="loader"></${this.loaderTag}>` : undefined)}
1140
1191
 
@@ -1143,12 +1194,12 @@ let AuroButton$1 = class AuroButton extends AuroElement$5 {
1143
1194
  <slot></slot>
1144
1195
  </span>
1145
1196
  </span>
1146
- </button>
1197
+ </${tag}>
1147
1198
  `;
1148
1199
  }
1149
1200
 
1150
1201
  /**
1151
- * Renders the layout of the button
1202
+ * Renders the layout of the button.
1152
1203
  * @returns {TemplateResult}
1153
1204
  * @private
1154
1205
  */
@@ -2662,6 +2713,18 @@ class AuroCounter extends i$2 {
2662
2713
 
2663
2714
  firstUpdated() {
2664
2715
  this.initValue();
2716
+ this.setTagAttribute("auro-counter");
2717
+ }
2718
+
2719
+ /**
2720
+ * Sets an attribute that matches the default tag name if the tag name is not the default.
2721
+ * @param {string} tagName - The tag name to set as an attribute.
2722
+ * @private
2723
+ */
2724
+ setTagAttribute(tagName) {
2725
+ if (this.tagName.toLowerCase() !== tagName) {
2726
+ this.setAttribute(tagName, true);
2727
+ }
2665
2728
  }
2666
2729
 
2667
2730
  /**
@@ -2702,14 +2765,24 @@ class AuroCounter extends i$2 {
2702
2765
  <label id="counter-label" class="label"><slot @slotchange="${this.onDefaultSlotChange}" ></slot></label>
2703
2766
  <slot id="counter-description" name="description"></slot>
2704
2767
  </div>
2705
- <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}">
2768
+ <div
2769
+ part="counterControl"
2770
+ aria-labelledby="counter-label"
2771
+ aria-describedby="counter-description"
2772
+ tabindex="${this.disabled ? '-1' : '0'}"
2773
+ role="spinbutton"
2774
+ aria-valuemin="${this.min}"
2775
+ aria-valuemax="${this.max}"
2776
+ aria-valuenow="${this.value}"
2777
+ aria-valuetext="${this.value !== undefined ? this.value : this.min}"
2778
+ >
2706
2779
  <auro-counter-button
2707
- aria-hidden="true"
2708
- tindex="-1"
2709
- part="controlMinus"
2710
- @click="${() => this.decrement()}"
2711
- ?onDark="${this.onDark}"
2712
- ?disabled="${this.disabled || this.disableMin || this.isIncrementDisabled(this.min)}"
2780
+ aria-hidden="true"
2781
+ .tabindex="${'-1'}"
2782
+ part="controlMinus"
2783
+ @click="${() => this.decrement()}"
2784
+ ?onDark="${this.onDark}"
2785
+ ?disabled="${this.disabled || this.disableMin || this.isIncrementDisabled(this.min)}"
2713
2786
  >
2714
2787
  <${this.iconTag} class="controlIcon" customSvg> ${IconUtil.generateSvgHtml(minusIcon)} </${this.iconTag}>
2715
2788
  </auro-counter-button>
@@ -2719,12 +2792,12 @@ class AuroCounter extends i$2 {
2719
2792
  </div>
2720
2793
 
2721
2794
  <auro-counter-button
2722
- aria-hidden="true"
2723
- tindex="-1"
2724
- part="controlPlus"
2725
- @click="${() => this.increment()}"
2726
- ?onDark="${this.onDark}"
2727
- ?disabled="${this.disabled || this.disableMax || this.isIncrementDisabled(this.max)}"
2795
+ aria-hidden="true"
2796
+ .tabindex="${'-1'}"
2797
+ part="controlPlus"
2798
+ @click="${() => this.increment()}"
2799
+ ?onDark="${this.onDark}"
2800
+ ?disabled="${this.disabled || this.disableMax || this.isIncrementDisabled(this.max)}"
2728
2801
  >
2729
2802
  <${this.iconTag} class="controlIcon" customSvg> ${IconUtil.generateSvgHtml(plusIcon)} </${this.iconTag}>
2730
2803
  </auro-counter-button>
@@ -7351,7 +7424,6 @@ let AuroElement$1 = class AuroElement extends i$2 {
7351
7424
  * @private
7352
7425
  */
7353
7426
  wrapper: {
7354
- type: HTMLElement,
7355
7427
  attribute: false,
7356
7428
  reflect: false
7357
7429
  }
@@ -7660,7 +7732,7 @@ class AuroLoader extends i$2 {
7660
7732
 
7661
7733
  var loaderVersion = '5.0.0';
7662
7734
 
7663
- /* eslint-disable max-lines, curly */
7735
+ /* eslint-disable max-lines, curly, jsdoc/no-undefined-types */
7664
7736
  // Copyright (c) Alaska Air. All right reserved. Licensed under the Apache-2.0 license
7665
7737
  // See LICENSE in the project root for license information.
7666
7738
 
@@ -7727,6 +7799,21 @@ class AuroButton extends AuroElement$1 {
7727
7799
  * @private
7728
7800
  */
7729
7801
  this.loaderTag = versioning.generateTag('auro-loader', loaderVersion, AuroLoader);
7802
+
7803
+ /**
7804
+ * @private
7805
+ */
7806
+ this.buttonHref = undefined;
7807
+
7808
+ /**
7809
+ * @private
7810
+ */
7811
+ this.buttonTarget = undefined;
7812
+
7813
+ /**
7814
+ * @private
7815
+ */
7816
+ this.buttonRel = undefined;
7730
7817
  }
7731
7818
 
7732
7819
  static get styles() {
@@ -7793,13 +7880,23 @@ class AuroButton extends AuroElement$1 {
7793
7880
  },
7794
7881
 
7795
7882
  /**
7796
- * Populates `tabIndex` to define the focusable sequence in keyboard navigation.
7883
+ * Populates `tabindex` to define the focusable sequence in keyboard navigation.
7797
7884
  */
7798
7885
  tIndex: {
7799
7886
  type: String,
7800
7887
  reflect: true
7801
7888
  },
7802
7889
 
7890
+ /**
7891
+ * Populates `tabindex` to define the focusable sequence in keyboard navigation.
7892
+ * Must be used with "." to ensure the host element does not retain a reference to the `tabindex` attribute.
7893
+ * Example: `<auro-button .tabindex="${this.disabled ? '-1' : '0'}"></auro-button>`
7894
+ */
7895
+ tabindex: {
7896
+ type: String,
7897
+ reflect: false
7898
+ },
7899
+
7803
7900
  /**
7804
7901
  * Sets title attribute. The information is most often shown as a tooltip text when the mouse moves over the element.
7805
7902
  */
@@ -7832,6 +7929,27 @@ class AuroButton extends AuroElement$1 {
7832
7929
  type: String,
7833
7930
  reflect: true
7834
7931
  },
7932
+
7933
+ /**
7934
+ * @private
7935
+ */
7936
+ buttonHref: {
7937
+ type: String,
7938
+ },
7939
+
7940
+ /**
7941
+ * @private
7942
+ */
7943
+ buttonTarget: {
7944
+ type: String,
7945
+ },
7946
+
7947
+ /**
7948
+ * @private
7949
+ */
7950
+ buttonRel: {
7951
+ type: String,
7952
+ },
7835
7953
  };
7836
7954
  }
7837
7955
 
@@ -7922,14 +8040,17 @@ class AuroButton extends AuroElement$1 {
7922
8040
  loading: this.loading,
7923
8041
  };
7924
8042
 
8043
+ const tag = this.buttonHref ? i$1`a` : i$1`button`;
8044
+ const part = this.buttonHref ? 'link' : 'button';
8045
+
7925
8046
  return u`
7926
- <button
7927
- part="button"
8047
+ <${tag}
8048
+ part="${part}"
7928
8049
  aria-label="${o(this.loading ? this.loadingText : this.currentAriaLabel || undefined)}"
7929
8050
  aria-labelledby="${o(this.loading ? undefined : this.currentAriaLabelledBy || undefined)}"
7930
- tabIndex="${o(this.tIndex)}"
8051
+ tabindex="${o(this.tIndex || this.tabindex)}"
7931
8052
  ?autofocus="${this.autofocus}"
7932
- class="${e(classes)}"
8053
+ class=${e(classes)}
7933
8054
  ?disabled="${this.disabled || this.loading}"
7934
8055
  ?onDark="${this.onDark}"
7935
8056
  title="${o(this.title ? this.title : undefined)}"
@@ -7938,6 +8059,9 @@ class AuroButton extends AuroElement$1 {
7938
8059
  variant="${o(this.variant ? this.variant : undefined)}"
7939
8060
  .value="${o(this.value ? this.value : undefined)}"
7940
8061
  @click="${this.type === 'submit' ? this.surfaceSubmitEvent : undefined}"
8062
+ href="${o(this.buttonHref || undefined)}"
8063
+ target="${o(this.buttonTarget || undefined)}"
8064
+ rel="${o(this.buttonRel || undefined)}"
7941
8065
  >
7942
8066
  ${o(this.loading ? u`<${this.loaderTag} pulse part="loader"></${this.loaderTag}>` : undefined)}
7943
8067
 
@@ -7946,12 +8070,12 @@ class AuroButton extends AuroElement$1 {
7946
8070
  <slot></slot>
7947
8071
  </span>
7948
8072
  </span>
7949
- </button>
8073
+ </${tag}>
7950
8074
  `;
7951
8075
  }
7952
8076
 
7953
8077
  /**
7954
- * Renders the layout of the button
8078
+ * Renders the layout of the button.
7955
8079
  * @returns {TemplateResult}
7956
8080
  * @private
7957
8081
  */
@@ -7960,7 +8084,7 @@ class AuroButton extends AuroElement$1 {
7960
8084
  }
7961
8085
  }
7962
8086
 
7963
- var buttonVersion = '11.0.0';
8087
+ var buttonVersion = '11.2.1';
7964
8088
 
7965
8089
  // Copyright (c) 2020 Alaska Airlines. All right reserved. Licensed under the Apache-2.0 license
7966
8090
  // See LICENSE in the project root for license information.
@@ -9073,7 +9197,266 @@ class AuroElement extends i$2 {
9073
9197
  }
9074
9198
  }
9075
9199
 
9076
- /* eslint-disable lit/no-invalid-html, lit/binding-positions, max-lines, prefer-destructuring, no-underscore-dangle, arrow-parens, no-confusing-arrow, curly */
9200
+ // Selectors for focusable elements
9201
+ const FOCUSABLE_SELECTORS = [
9202
+ 'a[href]',
9203
+ 'button:not([disabled])',
9204
+ 'textarea:not([disabled])',
9205
+ 'input:not([disabled])',
9206
+ 'select:not([disabled])',
9207
+ '[role="tab"]:not([disabled])',
9208
+ '[role="link"]:not([disabled])',
9209
+ '[role="button"]:not([disabled])',
9210
+ '[tabindex]:not([tabindex="-1"])',
9211
+ '[contenteditable]:not([contenteditable="false"])'
9212
+ ];
9213
+
9214
+ // List of custom components that are known to be focusable
9215
+ const FOCUSABLE_COMPONENTS = [
9216
+ 'auro-checkbox',
9217
+ 'auro-radio',
9218
+ 'auro-dropdown',
9219
+ 'auro-button',
9220
+ 'auro-combobox',
9221
+ 'auro-input',
9222
+ 'auro-counter',
9223
+ 'auro-menu',
9224
+ 'auro-select',
9225
+ 'auro-datepicker',
9226
+ 'auro-hyperlink',
9227
+ 'auro-accordion',
9228
+ ];
9229
+
9230
+ /**
9231
+ * Determines if a given element is a custom focusable component.
9232
+ * Returns true if the element matches a known focusable component and is not disabled.
9233
+ *
9234
+ * @param {HTMLElement} element The element to check for focusability.
9235
+ * @returns {boolean} True if the element is a focusable custom component, false otherwise.
9236
+ */
9237
+ function isFocusableComponent(element) {
9238
+ const componentName = element.tagName.toLowerCase();
9239
+
9240
+ // Guard Clause: Element is a focusable component
9241
+ if (!FOCUSABLE_COMPONENTS.some((name) => element.hasAttribute(name) || componentName === name)) return false;
9242
+
9243
+ // Guard Clause: Element is not disabled
9244
+ if (element.hasAttribute('disabled')) return false;
9245
+
9246
+ // Guard Clause: The element is a hyperlink and has no href attribute
9247
+ if (componentName.match("hyperlink") && !element.hasAttribute('href')) return false;
9248
+
9249
+ // If all guard clauses pass, the element is a focusable component
9250
+ return true;
9251
+ }
9252
+
9253
+ /**
9254
+ * Retrieves all focusable elements within the container in DOM order, including those in shadow DOM and slots.
9255
+ * Returns a unique, ordered array of elements that can receive focus.
9256
+ *
9257
+ * @param {HTMLElement} container The container to search within
9258
+ * @returns {Array<HTMLElement>} An array of focusable elements within the container.
9259
+ */
9260
+ function getFocusableElements(container) {
9261
+ // Get elements in DOM order by walking the tree
9262
+ const orderedFocusableElements = [];
9263
+
9264
+ // Define a recursive function to collect focusable elements in DOM order
9265
+ const collectFocusableElements = (root) => {
9266
+ // Check if current element is focusable
9267
+ if (root.nodeType === Node.ELEMENT_NODE) {
9268
+ // Check if this is a custom component that is focusable
9269
+ const isComponentFocusable = isFocusableComponent(root);
9270
+
9271
+ if (isComponentFocusable) {
9272
+ // Add the component itself as a focusable element and don't traverse its shadow DOM
9273
+ orderedFocusableElements.push(root);
9274
+ return; // Skip traversing inside this component
9275
+ }
9276
+
9277
+ // Check if the element itself matches any selector
9278
+ for (const selector of FOCUSABLE_SELECTORS) {
9279
+ if (root.matches?.(selector)) {
9280
+ orderedFocusableElements.push(root);
9281
+ break; // Once we know it's focusable, no need to check other selectors
9282
+ }
9283
+ }
9284
+
9285
+ // Process shadow DOM only for non-Auro components
9286
+ if (root.shadowRoot) {
9287
+ // Process shadow DOM children in order
9288
+ if (root.shadowRoot.children) {
9289
+ Array.from(root.shadowRoot.children).forEach(child => {
9290
+ collectFocusableElements(child);
9291
+ });
9292
+ }
9293
+ }
9294
+
9295
+ // Process slots and their assigned nodes in order
9296
+ if (root.tagName === 'SLOT') {
9297
+ const assignedNodes = root.assignedNodes({ flatten: true });
9298
+ for (const node of assignedNodes) {
9299
+ collectFocusableElements(node);
9300
+ }
9301
+ } else {
9302
+ // Process light DOM children in order
9303
+ if (root.children) {
9304
+ Array.from(root.children).forEach(child => {
9305
+ collectFocusableElements(child);
9306
+ });
9307
+ }
9308
+ }
9309
+ }
9310
+ };
9311
+
9312
+ // Start the traversal from the container
9313
+ collectFocusableElements(container);
9314
+
9315
+ // Remove duplicates that might have been collected through different paths
9316
+ // while preserving order
9317
+ const uniqueElements = [];
9318
+ const seen = new Set();
9319
+
9320
+ for (const element of orderedFocusableElements) {
9321
+ if (!seen.has(element)) {
9322
+ seen.add(element);
9323
+ uniqueElements.push(element);
9324
+ }
9325
+ }
9326
+
9327
+ return uniqueElements;
9328
+ }
9329
+
9330
+ /**
9331
+ * FocusTrap manages keyboard focus within a specified container element, ensuring that focus does not leave the container when tabbing.
9332
+ * It is commonly used for modal dialogs or overlays to improve accessibility by trapping focus within interactive UI components.
9333
+ */
9334
+ class FocusTrap {
9335
+ /**
9336
+ * Creates a new FocusTrap instance for the given container element.
9337
+ * Initializes event listeners and prepares the container for focus management.
9338
+ *
9339
+ * @param {HTMLElement} container The DOM element to trap focus within.
9340
+ * @throws {Error} If the provided container is not a valid HTMLElement.
9341
+ */
9342
+ constructor(container) {
9343
+ if (!container || !(container instanceof HTMLElement)) {
9344
+ throw new Error("FocusTrap requires a valid HTMLElement.");
9345
+ }
9346
+
9347
+ this.container = container;
9348
+ this.tabDirection = 'forward'; // or 'backward'
9349
+
9350
+ this._init();
9351
+ }
9352
+
9353
+ /**
9354
+ * Initializes the focus trap by setting up event listeners and attributes on the container.
9355
+ * Prepares the container for focus management, including support for shadow DOM and inert attributes.
9356
+ *
9357
+ * @private
9358
+ */
9359
+ _init() {
9360
+
9361
+ // Add inert attribute to prevent focusing programmatically as well (if supported)
9362
+ if ('inert' in HTMLElement.prototype) {
9363
+ this.container.inert = false; // Ensure the container isn't inert
9364
+ this.container.setAttribute('data-focus-trap-container', true); // Mark for identification
9365
+ }
9366
+
9367
+ // Track tab direction
9368
+ this.container.addEventListener('keydown', this._onKeydown);
9369
+ }
9370
+
9371
+ /**
9372
+ * Handles keydown events to manage tab navigation within the container.
9373
+ * Ensures that focus wraps around when reaching the first or last focusable element.
9374
+ *
9375
+ * @param {KeyboardEvent} e The keyboard event triggered by user interaction.
9376
+ * @private
9377
+ */
9378
+ _onKeydown = (e) => {
9379
+
9380
+ if (e.key === 'Tab') {
9381
+
9382
+ // Set the tab direction based on the key pressed
9383
+ this.tabDirection = e.shiftKey ? 'backward' : 'forward';
9384
+
9385
+ // Get the active element(s) in the document and shadow root
9386
+ // This will include the active element in the shadow DOM if it exists
9387
+ // Active element may be inside the shadow DOM depending on delegatesFocus, so we need to check both
9388
+ const actives = [
9389
+ document.activeElement,
9390
+ ...document.activeElement.shadowRoot && [document.activeElement.shadowRoot.activeElement] || []
9391
+ ];
9392
+
9393
+ // Update the focusable elements
9394
+ const focusables = this._getFocusableElements();
9395
+
9396
+ // If we're at either end of the focusable elements, wrap around to the other end
9397
+ const focusIndex =
9398
+ (actives.includes(focusables[0]) || actives.includes(this.container)) && this.tabDirection === 'backward'
9399
+ ? focusables.length - 1
9400
+ : actives.includes(focusables[focusables.length - 1]) && this.tabDirection === 'forward'
9401
+ ? 0
9402
+ : null;
9403
+
9404
+ if (focusIndex !== null) {
9405
+ focusables[focusIndex].focus();
9406
+ e.preventDefault(); // Prevent default tab behavior
9407
+ e.stopPropagation(); // Stop the event from bubbling up
9408
+ }
9409
+ }
9410
+ };
9411
+
9412
+ /**
9413
+ * Retrieves all focusable elements within the container in DOM order, including those in shadow DOM and slots.
9414
+ * Returns a unique, ordered array of elements that can receive focus.
9415
+ *
9416
+ * @returns {Array<HTMLElement>} An array of focusable elements within the container.
9417
+ * @private
9418
+ */
9419
+ _getFocusableElements() {
9420
+ // Use the imported utility function to get focusable elements
9421
+ const elements = getFocusableElements(this.container);
9422
+
9423
+ // Filter out any elements with the 'focus-bookend' class
9424
+ return elements;
9425
+ }
9426
+
9427
+ /**
9428
+ * Moves focus to the first focusable element within the container.
9429
+ * Useful for setting initial focus when activating the focus trap.
9430
+ */
9431
+ focusFirstElement() {
9432
+ const focusables = this._getFocusableElements();
9433
+ if (focusables.length) focusables[0].focus();
9434
+ }
9435
+
9436
+ /**
9437
+ * Moves focus to the last focusable element within the container.
9438
+ * Useful for setting focus when deactivating or cycling focus in reverse.
9439
+ */
9440
+ focusLastElement() {
9441
+ const focusables = this._getFocusableElements();
9442
+ if (focusables.length) focusables[focusables.length - 1].focus();
9443
+ }
9444
+
9445
+ /**
9446
+ * Removes event listeners and attributes added by the focus trap.
9447
+ * Call this method to clean up when the focus trap is no longer needed.
9448
+ */
9449
+ disconnect() {
9450
+
9451
+ if (this.container.hasAttribute('data-focus-trap-container')) {
9452
+ this.container.removeAttribute('data-focus-trap-container');
9453
+ }
9454
+
9455
+ this.container.removeEventListener('keydown', this._onKeydown);
9456
+ }
9457
+ }
9458
+
9459
+ /* 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 */
9077
9460
 
9078
9461
 
9079
9462
  /**
@@ -9132,6 +9515,11 @@ class AuroCounterGroup extends AuroElement {
9132
9515
  */
9133
9516
  this.validation = new AuroFormValidation();
9134
9517
 
9518
+ // Bind callback methods since we can't use arrow functions in class properties
9519
+
9520
+ /** @private */
9521
+ this.handleDropdownToggle = this.handleDropdownToggle.bind(this);
9522
+
9135
9523
  /**
9136
9524
  * Generate unique names for dependency components.
9137
9525
  * @private
@@ -9171,6 +9559,8 @@ class AuroCounterGroup extends AuroElement {
9171
9559
  static get properties() {
9172
9560
  return {
9173
9561
 
9562
+ ...super.properties,
9563
+
9174
9564
  /**
9175
9565
  * If declared, bib's position will be automatically calculated where to appear.
9176
9566
  * @default false
@@ -9309,51 +9699,6 @@ class AuroCounterGroup extends AuroElement {
9309
9699
  };
9310
9700
  }
9311
9701
 
9312
- /**
9313
- * Traps keyboard tab interactions within dropdown when open.
9314
- * @private
9315
- * @param {KeyboardEvent} event - The keyboard event.
9316
- * @param {NodeList} counters - The list of counter elements.
9317
- */
9318
- trapKeyboard(event, counters) {
9319
- if (!this.dropdown.isPopoverVisible) {
9320
- return;
9321
- }
9322
-
9323
- event.stopPropagation();
9324
- event.preventDefault();
9325
-
9326
- const firstFocusable = counters[0];
9327
- const lastFocusable = counters[counters.length - 1];
9328
-
9329
- if (event.key === 'Enter') {
9330
- firstFocusable.focus();
9331
- }
9332
-
9333
- if (event.key === 'Escape') {
9334
- this.dropdown.hide();
9335
- }
9336
-
9337
- if (event.key === 'Tab' && this.dropdown && event.target.offsetParent === this.dropdown.bib) {
9338
- this.dropdown.noHideOnThisFocusLoss = true;
9339
-
9340
- const currentIndex = Array.from(counters).indexOf(document.activeElement);
9341
-
9342
- if (event.shiftKey) {
9343
- if (currentIndex === 0) {
9344
- lastFocusable.focus();
9345
- } else {
9346
- counters[currentIndex - 1].focus();
9347
- }
9348
- } else if (currentIndex === counters.length - 1) {
9349
- firstFocusable.focus();
9350
- } else {
9351
- counters[currentIndex + 1].focus();
9352
- }
9353
-
9354
- }
9355
- }
9356
-
9357
9702
  /**
9358
9703
  * Dynamically disables increment/decrement buttons on a counter based on group value.
9359
9704
  * This method checks the total aggregated value against the group's min and max properties.
@@ -9387,6 +9732,52 @@ class AuroCounterGroup extends AuroElement {
9387
9732
  });
9388
9733
  }
9389
9734
 
9735
+ /**
9736
+ * Performs state updates that should happen when the dropdown is toggled.
9737
+ * @returns {void}
9738
+ * @private
9739
+ */
9740
+ handleDropdownToggle() {
9741
+
9742
+ // Check if the dropdown is open
9743
+ const dropdownIsOpen = this.dropdown.isPopoverVisible;
9744
+
9745
+ // Adds and removes the focus trap based on the dropdown state
9746
+ this.updateFocusTrap(dropdownIsOpen);
9747
+
9748
+ // Tasks to perform if the dropdown is closed
9749
+ if (!dropdownIsOpen) {
9750
+
9751
+ // Shift focus to the dropdown trigger
9752
+ this.dropdown.trigger.focus();
9753
+ }
9754
+ }
9755
+
9756
+ /**
9757
+ * Updates the focus trap based on whether the dropdown is open or closed.
9758
+ * If the dropdown is open, it creates a new focus trap and focuses the first element
9759
+ * If the dropdown is closed, it disconnects the focus trap if it exists to prevent memory leaks and disable focus trapping.
9760
+ * @param {boolean} dropdownIsOpen - Indicates whether the dropdown is currently open.
9761
+ * @returns {void}
9762
+ * @private
9763
+ */
9764
+ updateFocusTrap(dropdownIsOpen) {
9765
+
9766
+ // If the dropdown is open, create a focus trap and focus the first element
9767
+ if (dropdownIsOpen) {
9768
+ this.dropdownFocusTrap = new FocusTrap(this.dropdown.bibContent);
9769
+ this.dropdownFocusTrap.focusFirstElement();
9770
+ return;
9771
+ }
9772
+
9773
+ // Guard Clause: Ensure there is a focus trap currently active before continuing
9774
+ if (!this.dropdownFocusTrap) return;
9775
+
9776
+ // If the dropdown is not open, disconnect the focus trap if it exists
9777
+ this.dropdownFocusTrap.disconnect();
9778
+ this.dropdownFocusTrap = undefined;
9779
+ }
9780
+
9390
9781
  /**
9391
9782
  * Configures the dropdown counters by selecting all `auro-counter` elements,
9392
9783
  * appending them to the `auro-counter-wrapper` element within the shadow DOM,
@@ -9395,28 +9786,14 @@ class AuroCounterGroup extends AuroElement {
9395
9786
  */
9396
9787
  configureDropdownCounters() {
9397
9788
  this.dropdown = this.shadowRoot.querySelector(this.dropdownTag._$litStatic$);
9398
- this.dropdown.addEventListener('keydown', (event) => this.trapKeyboard(event, this.counters, 'dropdown'));
9399
- // notify dropdown to reconfigure as the trigger text is updated
9400
9789
  this.dropdown.requestUpdate();
9401
9790
 
9402
- this.addEventListener('auroDropdown-toggled', () => {
9403
- if (!this.dropdown.isPopoverVisible) {
9404
- this.dropdown.focus();
9405
- }
9406
- });
9791
+ this.dropdown.addEventListener("auroDropdown-toggled", this.handleDropdownToggle);
9407
9792
 
9408
9793
  const counterWrapper = this.shadowRoot.querySelector('auro-counter-wrapper');
9409
9794
  const counterSlot = counterWrapper.querySelector('slot');
9410
9795
  this.counters = counterSlot.assignedElements().filter(el => el.tagName.toLowerCase() === 'auro-counter' || el.hasAttribute('auro-counter'));
9411
9796
 
9412
- if (this.keydownHandler) {
9413
- counterWrapper.removeEventListener('keydown', this.keydownHandler);
9414
- }
9415
- this.keydownHandler = (keydownEvent) => {
9416
- this.trapKeyboard(keydownEvent, this.counters);
9417
- };
9418
- counterWrapper.addEventListener('keydown', this.keydownHandler);
9419
-
9420
9797
  this.counters.forEach((counter) => {
9421
9798
  counter.addEventListener("input", () => this.updateValue());
9422
9799
  });
@@ -9558,6 +9935,13 @@ class AuroCounterGroup extends AuroElement {
9558
9935
  this.updateValueText();
9559
9936
  }
9560
9937
 
9938
+ disconnectedCallback() {
9939
+ super.disconnectedCallback();
9940
+
9941
+ // Remove the event listener for dropdown toggling
9942
+ this.removeEventListener("auroDropdown-toggled", this.handleDropdownToggle);
9943
+ }
9944
+
9561
9945
  /**
9562
9946
  * Registers the custom element with the browser.
9563
9947
  * @param {string} [name="auro-counter-group"] - Custom element name to register.
@@ -9576,6 +9960,7 @@ class AuroCounterGroup extends AuroElement {
9576
9960
  renderCounterDropdown() {
9577
9961
  return u`
9578
9962
  <${this.dropdownTag}
9963
+ noHideOnThisFocusLoss
9579
9964
  chevron common fluid
9580
9965
  part="dropdown"
9581
9966
  ?autoPlacement="${this.autoPlacement}"