@jsenv/dom 0.12.1 → 0.12.2

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 (2) hide show
  1. package/dist/jsenv_dom.js +34 -4
  2. package/package.json +1 -1
package/dist/jsenv_dom.js CHANGED
@@ -5447,10 +5447,36 @@ const performTabNavigation = (
5447
5447
  return elementIsFocusable(element, { excludeAriaHidden });
5448
5448
  };
5449
5449
 
5450
- const predicate = (candidate) => {
5451
- const canBeFocusedByTab = isFocusableByTab(candidate);
5452
- // debug(`Testing`, candidate, `${canBeFocusedByTab ? "✓" : "✗"}`);
5453
- return canBeFocusedByTab;
5450
+ // A focus group "owns" the activeElement when activeElement is inside it.
5451
+ // From the inside, Tab should exit the group (skip its remaining children).
5452
+ // From the outside, Tab should enter the group normally (first focusable child).
5453
+ const activeFocusGroup =
5454
+ activeElement.closest?.("[navi-focus-group]") || null;
5455
+ const isOwnedByActiveFocusGroup = (el) =>
5456
+ activeFocusGroup && activeFocusGroup.contains(el);
5457
+
5458
+ const predicate = (candidate, skip) => {
5459
+ if (!isFocusableByTab(candidate)) {
5460
+ return false;
5461
+ }
5462
+ // Focus group roots are composite widgets.
5463
+ if (candidate.hasAttribute("navi-focus-group")) {
5464
+ if (isFocusableByTab(candidate)) {
5465
+ // Root has tabindex="0": it is the single Tab stop for the group.
5466
+ // Skip its children — arrow keys handle internal navigation.
5467
+ skip?.();
5468
+ return true;
5469
+ }
5470
+ // Root is not focusable by Tab: descend into children to allow Tab entry.
5471
+ return false;
5472
+ }
5473
+ // If candidate is inside the focus group that currently owns focus, skip
5474
+ // it — Tab should exit the group. (Going *into* a different focus group
5475
+ // is allowed: only one focus group at a time has the activeElement.)
5476
+ if (isOwnedByActiveFocusGroup(candidate)) {
5477
+ return false;
5478
+ }
5479
+ return true;
5454
5480
  };
5455
5481
 
5456
5482
  const activeElementIsRoot = activeElement === rootElement;
@@ -5594,6 +5620,10 @@ const initFocusGroup = (
5594
5620
  name, // Store undefined as-is for implicit grouping
5595
5621
  });
5596
5622
  cleanupCallbackSet.add(removeFocusGroup);
5623
+ element.setAttribute("navi-focus-group", "");
5624
+ cleanupCallbackSet.add(() => {
5625
+ element.removeAttribute("navi-focus-group");
5626
+ });
5597
5627
 
5598
5628
  tab: {
5599
5629
  if (!skipTab) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/dom",
3
- "version": "0.12.1",
3
+ "version": "0.12.2",
4
4
  "type": "module",
5
5
  "description": "DOM utilities for writing frontend code",
6
6
  "repository": {