# usePopupMenu Activation-Point Anchors

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

Use an activation-point anchor when a context menu must open near a pointer or at a predictable keyboard position and continue following its trigger during scrolling.

## Quick Start

1. Register each trigger element by id.
2. Enable `anchor.activationPoint` and bind `menu.offset` to the Popup.
3. Capture the activation against the trigger before activating its id.
4. Keep visibility parent-owned through the registry.

```vue
<template>
  <table>
    <tbody>
      <tr
        v-for="row in rows"
        :key="row.id"
        :ref="(element) => registerRow(row.id, element)"
        class="k-table-row"
        tabindex="0"
        aria-haspopup="menu"
        aria-expanded="false"
        @click="openFromPointer(row.id, $event)"
        @keydown.enter.prevent="openFromKeyboard(row.id)"
        @keydown.space.prevent="openFromKeyboard(row.id)"
      >
        <td>{{ row.name }}</td>
      </tr>
    </tbody>
  </table>

  <Popup
    :show="rowRegistry.isActive.value"
    :offset="menu.offset.value"
    @close="menu.handlePopupClose"
  >
    <Menu
      ref="menuRef"
      :items="items"
      :vertical="true"
      @keydown.escape="menu.handleMenuEscape"
      @select="menu.handleMenuSelect"
    />
  </Popup>
</template>

<script setup lang="ts">
import { ref, type ComponentPublicInstance } from "vue";
import { Popup } from "@progress/kendo-vue-popup";
import { Menu } from "@progress/kendo-vue-layout";
import { usePopupMenu } from "@featherk/composables/menu";
import { useActiveIdRegistry } from "@featherk/composables/registry";

const rows = [
  { id: 1, name: "Alpha" },
  { id: 2, name: "Beta" },
];
const items = [{ text: "Open" }, { text: "Archive" }];
const rowRegistry = useActiveIdRegistry<number>();
const menuRef = ref<ComponentPublicInstance | null>(null);

// Step 2: pointer coordinates receive 12px of visual clearance. Keyboard
// activation defaults to x=8 and the trigger's vertical center.
const menu = usePopupMenu({
  isOpen: rowRegistry.isActive,
  triggerRef: rowRegistry.activeElement,
  menuRef,
  triggerMode: "row",
  anchor: {
    activationPoint: { pointerOffset: { x: -12, y: -12 } },
  },
  requestShow: () => {},
  requestHide: () => rowRegistry.deactivate(),
});

// Step 1: the registry resolves an id to the current DOM element, including
// after keyed or virtualized rendering replaces it.
const registerRow = (id: number, element: unknown) => {
  rowRegistry.register(id, element as HTMLElement | null);
};

const activate = (
  id: number,
  activation: Parameters<typeof menu.setAnchorActivation>[1],
) => {
  const row = rowRegistry.resolve(id);
  // Step 3: capture before activation because triggerRef is still null or may
  // still resolve to the previously active row.
  if (!row || !menu.setAnchorActivation(row, activation)) return;

  // Step 4: the registry remains the owner of open state.
  rowRegistry.activate(id);
};

const openFromPointer = (id: number, event: MouseEvent) => {
  activate(id, {
    triggerType: "click",
    coordinates: { clientX: event.clientX, clientY: event.clientY },
  });
};

const openFromKeyboard = (id: number) => {
  activate(id, { triggerType: "keyboard" });
};
</script>
```

## Keyboard Position

The default keyboard point is 8px inside the trigger's left edge and vertically centered. Override it when needed:

```ts
anchor: {
  activationPoint: {
    keyboardPosition: { x: 12, y: "bottom" },
  },
}
```

`y` accepts a number or `"top"`, `"center"`, or `"bottom"`.

## Behavior

- Pointer coordinates are converted to a trigger-relative point once.
- Scroll and resize measurements reconstruct that point from the trigger's current rectangle.
- The clipping threshold defaults to `0`; the menu stays open while any part of the trigger remains visible.
- A fully clipped trigger closes with `"anchor-hidden"` without restoring focus.
- Activation metadata is cleared whenever `isOpen` becomes false.
- Use custom `getRect(trigger)` instead when positioning does not originate from a pointer or keyboard activation.
