@jsenv/dom 0.12.1 → 0.12.3

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