@aurodesignsystem-dev/auro-formkit 0.0.0-pr1600.3 → 0.0.0-pr1602.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/components/checkbox/demo/customize.min.js +1 -1
  2. package/components/checkbox/demo/getting-started.min.js +1 -1
  3. package/components/checkbox/demo/index.min.js +1 -1
  4. package/components/checkbox/dist/index.js +1 -1
  5. package/components/checkbox/dist/registered.js +1 -1
  6. package/components/combobox/demo/customize.min.js +110 -4
  7. package/components/combobox/demo/getting-started.md +49 -0
  8. package/components/combobox/demo/getting-started.min.js +129 -4
  9. package/components/combobox/demo/index.min.js +110 -4
  10. package/components/combobox/dist/auro-combobox.d.ts +22 -0
  11. package/components/combobox/dist/index.js +94 -4
  12. package/components/combobox/dist/registered.js +94 -4
  13. package/components/counter/demo/api.md +2 -0
  14. package/components/counter/demo/customize.min.js +320 -22
  15. package/components/counter/demo/getting-started.html +3 -1
  16. package/components/counter/demo/getting-started.md +49 -0
  17. package/components/counter/demo/getting-started.min.js +8766 -0
  18. package/components/counter/demo/index.min.js +320 -22
  19. package/components/counter/dist/auro-counter-group.d.ts +6 -0
  20. package/components/counter/dist/auro-counter.d.ts +6 -0
  21. package/components/counter/dist/counterGroupKeyboardStrategy.d.ts +1 -0
  22. package/components/counter/dist/index.js +320 -22
  23. package/components/counter/dist/registered.js +320 -22
  24. package/components/datepicker/demo/customize.min.js +3 -3
  25. package/components/datepicker/demo/getting-started.html +3 -1
  26. package/components/datepicker/demo/getting-started.md +39 -0
  27. package/components/datepicker/demo/getting-started.min.js +37626 -0
  28. package/components/datepicker/demo/index.min.js +3 -3
  29. package/components/datepicker/dist/index.js +3 -3
  30. package/components/datepicker/dist/registered.js +3 -3
  31. package/components/dropdown/demo/customize.min.js +1 -1
  32. package/components/dropdown/demo/getting-started.min.js +2 -2
  33. package/components/dropdown/demo/index.min.js +1 -1
  34. package/components/dropdown/dist/index.js +1 -1
  35. package/components/dropdown/dist/registered.js +1 -1
  36. package/components/form/demo/customize.min.js +444 -35
  37. package/components/form/demo/getting-started.min.js +444 -35
  38. package/components/form/demo/index.min.js +444 -35
  39. package/components/form/demo/registerDemoDeps.min.js +438 -34
  40. package/components/form/dist/index.js +6 -1
  41. package/components/form/dist/registered.js +6 -1
  42. package/components/input/demo/customize.md +0 -37
  43. package/components/input/demo/customize.min.js +1 -10
  44. package/components/input/demo/getting-started.md +33 -0
  45. package/components/input/demo/getting-started.min.js +21 -1
  46. package/components/input/demo/index.min.js +1 -1
  47. package/components/input/dist/index.js +1 -1
  48. package/components/input/dist/registered.js +1 -1
  49. package/components/menu/demo/index.min.js +16 -0
  50. package/components/menu/dist/index.js +16 -0
  51. package/components/menu/dist/registered.js +16 -0
  52. package/components/radio/demo/customize.min.js +1 -1
  53. package/components/radio/demo/getting-started.min.js +1 -1
  54. package/components/radio/demo/index.min.js +1 -1
  55. package/components/radio/dist/index.js +1 -1
  56. package/components/radio/dist/registered.js +1 -1
  57. package/components/select/demo/customize.min.js +18 -2
  58. package/components/select/demo/getting-started.md +49 -0
  59. package/components/select/demo/getting-started.min.js +40 -3
  60. package/components/select/demo/index.min.js +18 -2
  61. package/components/select/dist/index.js +2 -2
  62. package/components/select/dist/registered.js +2 -2
  63. package/custom-elements.json +606 -566
  64. package/package.json +1 -1
@@ -1127,7 +1127,7 @@ let AuroHelpText$1 = class AuroHelpText extends i$2 {
1127
1127
  }
1128
1128
  };
1129
1129
 
1130
- var formkitVersion$1 = '202609011635';
1130
+ var formkitVersion$1 = '202609021726';
1131
1131
 
1132
1132
  // Copyright (c) 2026 Alaska Airlines. All rights reserved. Licensed under the Apache-2.0 license
1133
1133
  // See LICENSE in the project root for license information.
@@ -1485,6 +1485,17 @@ class AuroCounter extends i$2 {
1485
1485
  this.validation.validate(this, force);
1486
1486
  }
1487
1487
 
1488
+ /**
1489
+ * Resets the counter to its default state, clearing validity/touched state and returning
1490
+ * the value to `min` — matching the clear-on-reset behavior of the other Auro form elements.
1491
+ * @returns {void}
1492
+ */
1493
+ reset() {
1494
+ // validation.reset() clears validity/touched and sets value to undefined; initValue() then restores the min floor.
1495
+ this.validation.reset(this);
1496
+ this.initValue();
1497
+ }
1498
+
1488
1499
  firstUpdated() {
1489
1500
  this.initValue();
1490
1501
  this.setTagAttribute("auro-counter");
@@ -4473,7 +4484,7 @@ class AuroFloatingUI {
4473
4484
  }
4474
4485
 
4475
4486
  // Selectors for focusable elements
4476
- const FOCUSABLE_SELECTORS = [
4487
+ const FOCUSABLE_SELECTORS$1 = [
4477
4488
  "a[href]",
4478
4489
  "button:not([disabled])",
4479
4490
  "textarea:not([disabled])",
@@ -4487,7 +4498,7 @@ const FOCUSABLE_SELECTORS = [
4487
4498
  ];
4488
4499
 
4489
4500
  // List of custom components that are known to be focusable
4490
- const FOCUSABLE_COMPONENTS = [
4501
+ const FOCUSABLE_COMPONENTS$1 = [
4491
4502
  "auro-checkbox",
4492
4503
  "auro-radio",
4493
4504
  "auro-dropdown",
@@ -4509,12 +4520,12 @@ const FOCUSABLE_COMPONENTS = [
4509
4520
  * @param {HTMLElement} element The element to check for focusability.
4510
4521
  * @returns {boolean} True if the element is a focusable custom component, false otherwise.
4511
4522
  */
4512
- function isFocusableComponent(element) {
4523
+ function isFocusableComponent$1(element) {
4513
4524
  const componentName = element.tagName.toLowerCase();
4514
4525
 
4515
4526
  // Guard Clause: Element is a focusable component
4516
4527
  if (
4517
- !FOCUSABLE_COMPONENTS.some(
4528
+ !FOCUSABLE_COMPONENTS$1.some(
4518
4529
  (name) => element.hasAttribute(name) || componentName === name,
4519
4530
  )
4520
4531
  )
@@ -4538,7 +4549,7 @@ function isFocusableComponent(element) {
4538
4549
  * @param {HTMLElement} element The element whose tabindex to read.
4539
4550
  * @returns {?number} The numeric tabindex or null if missing/invalid.
4540
4551
  */
4541
- function getNumericTabIndex(element) {
4552
+ function getNumericTabIndex$1(element) {
4542
4553
  const raw = element.getAttribute("tabindex");
4543
4554
  if (raw == null) return null;
4544
4555
 
@@ -4554,7 +4565,7 @@ function getNumericTabIndex(element) {
4554
4565
  * @param {HTMLElement} container The container to search within
4555
4566
  * @returns {Array<HTMLElement>} An array of focusable elements within the container.
4556
4567
  */
4557
- function getFocusableElements(container) {
4568
+ function getFocusableElements$1(container) {
4558
4569
  // Get elements in DOM order by walking the tree
4559
4570
  const orderedFocusableElements = [];
4560
4571
 
@@ -4563,7 +4574,7 @@ function getFocusableElements(container) {
4563
4574
  // Check if current element is focusable
4564
4575
  if (root.nodeType === Node.ELEMENT_NODE) {
4565
4576
  // Check if this is a custom component that is focusable
4566
- const isComponentFocusable = isFocusableComponent(root);
4577
+ const isComponentFocusable = isFocusableComponent$1(root);
4567
4578
 
4568
4579
  if (isComponentFocusable) {
4569
4580
  // Add the component itself as a focusable element and don't traverse its shadow DOM
@@ -4572,7 +4583,7 @@ function getFocusableElements(container) {
4572
4583
  }
4573
4584
 
4574
4585
  // Check if the element itself matches any selector
4575
- for (const selector of FOCUSABLE_SELECTORS) {
4586
+ for (const selector of FOCUSABLE_SELECTORS$1) {
4576
4587
  if (root.matches?.(selector)) {
4577
4588
  orderedFocusableElements.push(root);
4578
4589
  break; // Once we know it's focusable, no need to check other selectors
@@ -4626,20 +4637,20 @@ function getFocusableElements(container) {
4626
4637
 
4627
4638
  // First extract elements with valid positive tabindex
4628
4639
  const elementsWithTabindex = uniqueElements.filter((el) => {
4629
- const tabindex = getNumericTabIndex(el);
4640
+ const tabindex = getNumericTabIndex$1(el);
4630
4641
  return tabindex !== null && tabindex > 0;
4631
4642
  });
4632
4643
 
4633
4644
  // Sort these elements by their tabindex value
4634
4645
  elementsWithTabindex.sort((a, b) => {
4635
- const aIndex = getNumericTabIndex(a) ?? 0;
4636
- const bIndex = getNumericTabIndex(b) ?? 0;
4646
+ const aIndex = getNumericTabIndex$1(a) ?? 0;
4647
+ const bIndex = getNumericTabIndex$1(b) ?? 0;
4637
4648
  return aIndex - bIndex;
4638
4649
  });
4639
4650
 
4640
4651
  // Elements without tabindex (preserving their original order)
4641
4652
  const elementsWithoutTabindex = uniqueElements.filter((el) => {
4642
- const tabindex = getNumericTabIndex(el);
4653
+ const tabindex = getNumericTabIndex$1(el);
4643
4654
 
4644
4655
  // Elements without tabindex or with tabindex of 0 stay in DOM order
4645
4656
  return tabindex === null || tabindex === 0;
@@ -5434,7 +5445,7 @@ class AuroHelpText extends i$2 {
5434
5445
  }
5435
5446
  }
5436
5447
 
5437
- var formkitVersion = '202609011635';
5448
+ var formkitVersion = '202609021726';
5438
5449
 
5439
5450
  /**
5440
5451
  * AuroElement is the shared base class for layout-aware Auro form components.
@@ -5734,7 +5745,7 @@ class AuroDropdown extends AuroElement$1 {
5734
5745
  */
5735
5746
  focus() {
5736
5747
  if (this.isPopoverVisible && this.bibContent) {
5737
- const focusables = getFocusableElements(this.bibContent);
5748
+ const focusables = getFocusableElements$1(this.bibContent);
5738
5749
  if (focusables.length > 0) {
5739
5750
  focusables[0].focus();
5740
5751
  }
@@ -5766,7 +5777,7 @@ class AuroDropdown extends AuroElement$1 {
5766
5777
  // wrappers like <div> containing a <button>). If none found, try
5767
5778
  // focusing the element directly (works for custom elements like
5768
5779
  // auro-input that have delegatesFocus or a custom focus() method).
5769
- const descendants = getFocusableElements(el);
5780
+ const descendants = getFocusableElements$1(el);
5770
5781
  if (descendants.length > 0) {
5771
5782
  descendants[0].focus();
5772
5783
  return;
@@ -5778,7 +5789,7 @@ class AuroDropdown extends AuroElement$1 {
5778
5789
  }
5779
5790
  }
5780
5791
  // Fallback: try DOM children (non-slotted content)
5781
- const focusables = getFocusableElements(this.trigger);
5792
+ const focusables = getFocusableElements$1(this.trigger);
5782
5793
  if (focusables.length > 0) {
5783
5794
  focusables[0].focus();
5784
5795
  }
@@ -6457,7 +6468,7 @@ class AuroDropdown extends AuroElement$1 {
6457
6468
  }
6458
6469
 
6459
6470
  // Collect focusable elements from the bib content.
6460
- const focusables = getFocusableElements(this.bibContent);
6471
+ const focusables = getFocusableElements$1(this.bibContent);
6461
6472
 
6462
6473
  // Fallback: try from slotted content directly
6463
6474
  if (!focusables.length) {
@@ -6466,7 +6477,7 @@ class AuroDropdown extends AuroElement$1 {
6466
6477
 
6467
6478
  for (const node of assignedNodes) {
6468
6479
  if (node.nodeType === Node.ELEMENT_NODE) {
6469
- focusables.push(...getFocusableElements(node));
6480
+ focusables.push(...getFocusableElements$1(node));
6470
6481
  }
6471
6482
  }
6472
6483
  }
@@ -6526,7 +6537,7 @@ class AuroDropdown extends AuroElement$1 {
6526
6537
 
6527
6538
  // Move initial focus into the bib content, matching FocusTrap behavior
6528
6539
  requestAnimationFrame(() => {
6529
- const focusables = getFocusableElements(this.bibContent);
6540
+ const focusables = getFocusableElements$1(this.bibContent);
6530
6541
  if (focusables.length) {
6531
6542
  focusables[0].focus();
6532
6543
  }
@@ -6539,7 +6550,7 @@ class AuroDropdown extends AuroElement$1 {
6539
6550
  // popover asynchronously — a synchronous focus() would target
6540
6551
  // zero-dimension elements and be silently ignored.
6541
6552
  requestAnimationFrame(() => {
6542
- const focusables = getFocusableElements(this.bibContent);
6553
+ const focusables = getFocusableElements$1(this.bibContent);
6543
6554
  if (focusables.length) {
6544
6555
  focusables[0].focus();
6545
6556
  }
@@ -6757,7 +6768,7 @@ class AuroDropdown extends AuroElement$1 {
6757
6768
  if (triggerContentNodes) {
6758
6769
 
6759
6770
  // See if any of them are focusable elements
6760
- this.triggerContentFocusable = triggerContentNodes.some((node) => getFocusableElements(node).length > 0);
6771
+ this.triggerContentFocusable = triggerContentNodes.some((node) => getFocusableElements$1(node).length > 0);
6761
6772
 
6762
6773
  // If any of them are focusable elements
6763
6774
  if (this.triggerContentFocusable) {
@@ -7519,9 +7530,241 @@ class AuroElement extends i$2 {
7519
7530
  }
7520
7531
  }
7521
7532
 
7533
+ // Selectors for focusable elements
7534
+ const FOCUSABLE_SELECTORS = [
7535
+ "a[href]",
7536
+ "button:not([disabled])",
7537
+ "textarea:not([disabled])",
7538
+ "input:not([disabled])",
7539
+ "select:not([disabled])",
7540
+ '[role="tab"]:not([disabled])',
7541
+ '[role="link"]:not([disabled])',
7542
+ '[role="button"]:not([disabled])',
7543
+ '[tabindex]:not([tabindex="-1"])',
7544
+ '[contenteditable]:not([contenteditable="false"])',
7545
+ ];
7546
+
7547
+ // List of custom components that are known to be focusable
7548
+ const FOCUSABLE_COMPONENTS = [
7549
+ "auro-checkbox",
7550
+ "auro-radio",
7551
+ "auro-dropdown",
7552
+ "auro-button",
7553
+ "auro-combobox",
7554
+ "auro-input",
7555
+ "auro-counter",
7556
+ // 'auro-menu', // Auro menu is not focusable by default, it uses a different interaction model
7557
+ "auro-select",
7558
+ "auro-datepicker",
7559
+ "auro-hyperlink",
7560
+ "auro-accordion",
7561
+ ];
7562
+
7563
+ /**
7564
+ * Determines if a given element is a custom focusable component.
7565
+ * Returns true if the element matches a known focusable component and is not disabled.
7566
+ *
7567
+ * @param {HTMLElement} element The element to check for focusability.
7568
+ * @returns {boolean} True if the element is a focusable custom component, false otherwise.
7569
+ */
7570
+ function isFocusableComponent(element) {
7571
+ const componentName = element.tagName.toLowerCase();
7572
+
7573
+ // Guard Clause: Element is a focusable component
7574
+ if (
7575
+ !FOCUSABLE_COMPONENTS.some(
7576
+ (name) => element.hasAttribute(name) || componentName === name,
7577
+ )
7578
+ )
7579
+ return false;
7580
+
7581
+ // Guard Clause: Element is not disabled
7582
+ if (element.hasAttribute("disabled")) return false;
7583
+
7584
+ // Guard Clause: The element is a hyperlink and has no href attribute
7585
+ if (componentName.match("hyperlink") && !element.hasAttribute("href"))
7586
+ return false;
7587
+
7588
+ // If all guard clauses pass, the element is a focusable component
7589
+ return true;
7590
+ }
7591
+
7592
+ /**
7593
+ * Safely get a numeric tabindex for an element.
7594
+ * Returns a number if the tabindex is a valid integer, otherwise null.
7595
+ *
7596
+ * @param {HTMLElement} element The element whose tabindex to read.
7597
+ * @returns {?number} The numeric tabindex or null if missing/invalid.
7598
+ */
7599
+ function getNumericTabIndex(element) {
7600
+ const raw = element.getAttribute("tabindex");
7601
+ if (raw == null) return null;
7602
+
7603
+ const value = Number.parseInt(raw, 10);
7604
+ return Number.isNaN(value) ? null : value;
7605
+ }
7606
+
7607
+ /**
7608
+ * Retrieves all focusable elements within the container in DOM order, including those in shadow DOM and slots.
7609
+ * Returns a unique, ordered array of elements that can receive focus.
7610
+ * Also sorts elements with tabindex first, preserving their order.
7611
+ *
7612
+ * @param {HTMLElement} container The container to search within
7613
+ * @returns {Array<HTMLElement>} An array of focusable elements within the container.
7614
+ */
7615
+ function getFocusableElements(container) {
7616
+ // Get elements in DOM order by walking the tree
7617
+ const orderedFocusableElements = [];
7618
+
7619
+ // Define a recursive function to collect focusable elements in DOM order
7620
+ const collectFocusableElements = (root) => {
7621
+ // Check if current element is focusable
7622
+ if (root.nodeType === Node.ELEMENT_NODE) {
7623
+ // Check if this is a custom component that is focusable
7624
+ const isComponentFocusable = isFocusableComponent(root);
7625
+
7626
+ if (isComponentFocusable) {
7627
+ // Add the component itself as a focusable element and don't traverse its shadow DOM
7628
+ orderedFocusableElements.push(root);
7629
+ return; // Skip traversing inside this component
7630
+ }
7631
+
7632
+ // Check if the element itself matches any selector
7633
+ for (const selector of FOCUSABLE_SELECTORS) {
7634
+ if (root.matches?.(selector)) {
7635
+ orderedFocusableElements.push(root);
7636
+ break; // Once we know it's focusable, no need to check other selectors
7637
+ }
7638
+ }
7639
+
7640
+ // Process shadow DOM only for non-Auro components
7641
+ if (root.shadowRoot) {
7642
+ // Process shadow DOM children in order
7643
+ if (root.shadowRoot.children) {
7644
+ Array.from(root.shadowRoot.children).forEach((child) => {
7645
+ collectFocusableElements(child);
7646
+ });
7647
+ }
7648
+ }
7649
+
7650
+ // Process slots and their assigned nodes in order
7651
+ if (root.tagName === "SLOT") {
7652
+ const assignedNodes = root.assignedNodes({ flatten: true });
7653
+ for (const node of assignedNodes) {
7654
+ collectFocusableElements(node);
7655
+ }
7656
+ } else {
7657
+ // Process light DOM children in order
7658
+ if (root.children) {
7659
+ Array.from(root.children).forEach((child) => {
7660
+ collectFocusableElements(child);
7661
+ });
7662
+ }
7663
+ }
7664
+ }
7665
+ };
7666
+
7667
+ // Start the traversal from the container
7668
+ collectFocusableElements(container);
7669
+
7670
+ // Remove duplicates that might have been collected through different paths
7671
+ // while preserving order
7672
+ const uniqueElements = [];
7673
+ const seen = new Set();
7674
+
7675
+ for (const element of orderedFocusableElements) {
7676
+ if (!seen.has(element)) {
7677
+ seen.add(element);
7678
+ uniqueElements.push(element);
7679
+ }
7680
+ }
7681
+
7682
+ // Move tab-indexed elements to the front while preserving their order
7683
+ // This ensures that elements with tabindex are prioritized in the focus order
7684
+
7685
+ // First extract elements with valid positive tabindex
7686
+ const elementsWithTabindex = uniqueElements.filter((el) => {
7687
+ const tabindex = getNumericTabIndex(el);
7688
+ return tabindex !== null && tabindex > 0;
7689
+ });
7690
+
7691
+ // Sort these elements by their tabindex value
7692
+ elementsWithTabindex.sort((a, b) => {
7693
+ const aIndex = getNumericTabIndex(a) ?? 0;
7694
+ const bIndex = getNumericTabIndex(b) ?? 0;
7695
+ return aIndex - bIndex;
7696
+ });
7697
+
7698
+ // Elements without tabindex (preserving their original order)
7699
+ const elementsWithoutTabindex = uniqueElements.filter((el) => {
7700
+ const tabindex = getNumericTabIndex(el);
7701
+
7702
+ // Elements without tabindex or with tabindex of 0 stay in DOM order
7703
+ return tabindex === null || tabindex === 0;
7704
+ });
7705
+
7706
+ // Combine both arrays with tabindex elements first
7707
+ const tabIndexedUniqueElements = [
7708
+ ...elementsWithTabindex,
7709
+ ...elementsWithoutTabindex,
7710
+ ];
7711
+
7712
+ return tabIndexedUniqueElements;
7713
+ }
7714
+
7522
7715
  // Copyright (c) 2026 Alaska Airlines. All rights reserved. Licensed under the Apache-2.0 license
7523
7716
  // See LICENSE in the project root for license information.
7524
7717
 
7718
+
7719
+ /**
7720
+ * Whether `el` is currently visible enough to receive focus. Prefers the
7721
+ * browser's `checkVisibility()`; otherwise combines a display/visibility check
7722
+ * with an ancestor-hidden probe that keeps position:fixed elements visible.
7723
+ * @param {Element} el - The element to test.
7724
+ * @returns {boolean}
7725
+ */
7726
+ function isVisibleForFocus(el) {
7727
+ if (typeof el.checkVisibility === 'function') {
7728
+ return el.checkVisibility();
7729
+ }
7730
+ const style = el.ownerDocument.defaultView.getComputedStyle(el);
7731
+ if (style.display === 'none' || style.visibility === 'hidden') {
7732
+ return false;
7733
+ }
7734
+ return el.offsetParent !== null || style.position === 'fixed';
7735
+ }
7736
+
7737
+ /**
7738
+ * Return the tab stop that comes before the counter-group in page tab order,
7739
+ * skipping any hidden entries the walker doesn't filter itself. The group host
7740
+ * itself is not a tab stop; its focusables (the internal dropdown and the
7741
+ * counters) are, so anchor the search on the group's earliest focusable.
7742
+ * @param {Element} component - The auro-counter-group host element.
7743
+ * @returns {Element|null}
7744
+ */
7745
+ function getPreviousTabStop(component) {
7746
+ const tabStops = getFocusableElements(component.ownerDocument.body);
7747
+ const counters = component.counters || [];
7748
+ const owned = [
7749
+ component,
7750
+ component.dropdown,
7751
+ ...counters
7752
+ ].filter(Boolean);
7753
+ let groupStart = tabStops.length;
7754
+ owned.forEach((node) => {
7755
+ const index = tabStops.indexOf(node);
7756
+ if (index >= 0 && index < groupStart) {
7757
+ groupStart = index;
7758
+ }
7759
+ });
7760
+ for (let index = groupStart - 1; index >= 0; index -= 1) {
7761
+ if (isVisibleForFocus(tabStops[index])) {
7762
+ return tabStops[index];
7763
+ }
7764
+ }
7765
+ return null;
7766
+ }
7767
+
7525
7768
  const counterGroupKeyboardStrategy = {
7526
7769
  Escape(component, evt) {
7527
7770
  if (!component.dropdown || !component.dropdown.isPopoverVisible) {
@@ -7535,6 +7778,48 @@ const counterGroupKeyboardStrategy = {
7535
7778
 
7536
7779
  component.dropdown.hide();
7537
7780
  },
7781
+
7782
+ Tab(component, evt, ctx) {
7783
+ // Forward Tab already exits past the bib content and the focusout handler
7784
+ // closes the bib. Fullscreen relies on the dialog's native focus trapping.
7785
+ if (!ctx.isExpanded || ctx.isModal || !evt.shiftKey) {
7786
+ return;
7787
+ }
7788
+
7789
+ // The trigger precedes the bib content in the flattened tab order, so a
7790
+ // native Shift+Tab off the first counter lands back on the trigger and
7791
+ // leaves the bib open. Only intercept when focus is on the first focusable
7792
+ // in the bib; otherwise let the browser move to the previous counter.
7793
+ // `composedPath()` reports the real event origin through every shadow
7794
+ // boundary, unlike `document.activeElement`, which retargets to the first
7795
+ // slotted counter and cannot tell the counters apart.
7796
+ const focusables = getFocusableElements(component.dropdown.bibContent);
7797
+ if (!focusables.length) {
7798
+ return;
7799
+ }
7800
+
7801
+ const path = evt.composedPath();
7802
+ const activeIndex = focusables.findIndex((el) => path.includes(el));
7803
+ if (activeIndex !== 0) {
7804
+ return;
7805
+ }
7806
+
7807
+ const previousTabStop = getPreviousTabStop(component);
7808
+
7809
+ // Always suppress the native Shift+Tab. Otherwise it fires against the bib
7810
+ // as it is torn down and lands focus on the trigger (which precedes the
7811
+ // first counter), re-opening the exact loop this handler prevents. When
7812
+ // nothing precedes the group, focus falls to the body instead of the
7813
+ // trigger, which reads as exiting the group backward.
7814
+ evt.preventDefault();
7815
+ component.hideBib();
7816
+
7817
+ if (!previousTabStop) {
7818
+ return;
7819
+ }
7820
+
7821
+ doubleRaf(() => previousTabStop.focus());
7822
+ },
7538
7823
  };
7539
7824
 
7540
7825
  /* eslint-disable lit/no-invalid-html, lit/binding-positions, max-lines, no-underscore-dangle, arrow-parens, no-confusing-arrow, curly, dot-location, no-inline-comments, line-comment-position,
@@ -8162,6 +8447,19 @@ class AuroCounterGroup extends AuroElement {
8162
8447
  this.validation.validate(this, force);
8163
8448
  }
8164
8449
 
8450
+ /**
8451
+ * Resets the counter group to its initial state by resetting each child counter and clearing validity.
8452
+ * Each child reset dispatches an `input` event, which triggers `updateValue()` to recompute `value`/`total`.
8453
+ * @returns {void}
8454
+ */
8455
+ reset() {
8456
+ if (this.counters) {
8457
+ this.counters.forEach((counter) => counter.reset());
8458
+ }
8459
+
8460
+ this.validation.reset(this);
8461
+ }
8462
+
8165
8463
  updated(changedProperties) {
8166
8464
  if (changedProperties.has("value")) {
8167
8465
  this.validate();
@@ -346,6 +346,12 @@ export class AuroCounterGroup extends AuroElement {
346
346
  * @param {boolean} [force=false] - Whether to force validation.
347
347
  */
348
348
  validate(force?: boolean): void;
349
+ /**
350
+ * Resets the counter group to its initial state by resetting each child counter and clearing validity.
351
+ * Each child reset dispatches an `input` event, which triggers `updateValue()` to recompute `value`/`total`.
352
+ * @returns {void}
353
+ */
354
+ reset(): void;
349
355
  updated(changedProperties: any): void;
350
356
  firstUpdated(): void;
351
357
  /**
@@ -129,6 +129,12 @@ export class AuroCounter extends LitElement {
129
129
  * @param {boolean} [force=false] - Whether to force validation.
130
130
  */
131
131
  validate(force?: boolean): void;
132
+ /**
133
+ * Resets the counter to its default state, clearing validity/touched state and returning
134
+ * the value to `min` — matching the clear-on-reset behavior of the other Auro form elements.
135
+ * @returns {void}
136
+ */
137
+ reset(): void;
132
138
  firstUpdated(): void;
133
139
  /**
134
140
  * Sets an attribute that matches the default tag name if the tag name is not the default.
@@ -1,3 +1,4 @@
1
1
  export namespace counterGroupKeyboardStrategy {
2
2
  function Escape(component: any, evt: any): void;
3
+ function Tab(component: any, evt: any, ctx: any): void;
3
4
  }