# usePopupMenu Focus and Exclusivity

[Back to usePopupMenu](https://github.com/NantHealth/featherk/blob/integration/packages/composables/docs/menu/usePopupMenu.md)

This guide covers keyboard focus restoration and coordination between independently configured popup menus.

## Focus Target Precedence

For keyboard menu selection and Escape, configured focus targets resolve in this order:

1. `resolveFocusTarget()`
2. `focusTargetRef`
3. `focusTargetContainerSelector`, resolved with `trigger.closest(selector)`
4. For Escape only: `escapeFocusTarget()`
5. For Escape only: `escapeFocusContainerSelector`
6. The trigger

Use the selector option for the common case where a button menu should return keyboard focus to its containing row or toolbar group:

```ts
const menu = usePopupMenu({
  isOpen,
  triggerRef,
  menuRef,
  triggerMode: "button",
  focusTargetContainerSelector: ".k-table-row",
  requestShow: () => (isOpen.value = true),
  requestHide: () => (isOpen.value = false),
});
```

The target is captured before `requestHide()` runs, so it remains available when shared state immediately clears `triggerRef`.

## Close Behavior

| Close path              | Focus behavior                                   |
| ----------------------- | ------------------------------------------------ |
| Keyboard menu selection | Configured focus target, then trigger            |
| Escape                  | Configured target, Escape fallback, then trigger |
| Pointer menu selection  | Trigger                                          |
| Outside click           | Browser retains control                          |
| Trigger click           | Browser retains control                          |
| Anchor fully clipped    | No focus restoration                             |

## Multiple Exclusive Menus

Keep exclusivity outside `usePopupMenu`. `useExclusiveGroup` coordinates registries without coupling popup lifecycle to application state.

```ts
import { ref } from "vue";
import { usePopupMenu } from "@featherk/composables/menu";
import {
  useActiveIdRegistry,
  useExclusiveGroup,
} from "@featherk/composables/registry";

const group = useExclusiveGroup();
const rowRegistry = useActiveIdRegistry<number>();
const buttonRegistry = useActiveIdRegistry<number>();
const rowMenuRef = ref<HTMLElement | null>(null);
const buttonMenuRef = ref<HTMLElement | null>(null);

group.register(rowRegistry);
group.register(buttonRegistry);

const rowMenu = usePopupMenu({
  isOpen: rowRegistry.isActive,
  triggerRef: rowRegistry.activeElement,
  menuRef: rowMenuRef,
  triggerMode: "row",
  anchor: { activationPoint: true },
  requestShow: () => {},
  requestHide: () => rowRegistry.deactivate(),
});

const buttonMenu = usePopupMenu({
  isOpen: buttonRegistry.isActive,
  triggerRef: buttonRegistry.activeElement,
  menuRef: buttonMenuRef,
  triggerMode: "button",
  anchor: true,
  focusTargetContainerSelector: ".k-table-row",
  requestShow: () => {},
  requestHide: () => buttonRegistry.deactivate(),
});

const openRowMenu = (id: number, row: HTMLElement, event: MouseEvent) => {
  group.deactivateOthers(rowRegistry);
  if (
    rowMenu.setAnchorActivation(row, {
      triggerType: "click",
      coordinates: { clientX: event.clientX, clientY: event.clientY },
    })
  ) {
    rowRegistry.activate(id);
  }
};

const openButtonMenu = (id: number) => {
  group.deactivateOthers(buttonRegistry);
  buttonRegistry.activate(id);
};
```

`useExclusiveGroup` changes only active registry state. Each `usePopupMenu` instance continues to own its own ARIA, positioning, close reason, and focus behavior.
