# usePopupMenu Shared Instances

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

Use one `usePopupMenu` instance for many equivalent triggers when only one item may own the menu at a time.

## Quick Start

1. Register every trigger with `useActiveIdRegistry`.
2. Pass `registry.isActive` and `registry.activeElement` to `usePopupMenu`.
3. Activate or deactivate ids in a consumer-owned toggle function.
4. Render one Popup and bind its `show`, `offset`, and close handler.

```vue
<template>
  <ul>
    <li v-for="record in records" :key="record.id">
      <button
        :ref="(element) => registerTrigger(record.id, element)"
        type="button"
        aria-haspopup="menu"
        aria-expanded="false"
        @click="toggleMenu(record.id)"
      >
        Actions for {{ record.name }}
      </button>
    </li>
  </ul>

  <Popup
    :show="registry.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 records = [
  { id: 1, name: "Alpha" },
  { id: 2, name: "Beta" },
];
const items = [{ text: "Open" }, { text: "Archive" }];
const registry = useActiveIdRegistry<number>();
const menuRef = ref<ComponentPublicInstance | null>(null);

// Step 2: one reactive pair follows whichever trigger id is active.
const menu = usePopupMenu({
  isOpen: registry.isActive,
  triggerRef: registry.activeElement,
  menuRef,
  triggerMode: "button",
  anchor: true,
  requestShow: () => {},
  requestHide: () => registry.deactivate(),
});

// Step 1: registration remains stable across keyed rerenders.
const registerTrigger = (id: number, element: unknown) => {
  registry.register(
    id,
    element as HTMLElement | ComponentPublicInstance | null,
  );
};

// Step 3: shared state must distinguish closing the active id from switching ids.
const toggleMenu = (id: number) => {
  if (registry.isIdActive(id)) {
    menu.handleTriggerClick();
    return;
  }

  if (registry.isActive.value) {
    menu.handleTriggerClick();
  }
  registry.activate(id);
};
</script>
```

## Why Offset Positioning

Bind the returned `offset` instead of Kendo Popup's `anchor` prop for a shared instance. Kendo resolves an anchor ref name when the Popup mounts; it does not dynamically retarget a different trigger element. `usePopupMenu` remeasures the active registered element on scroll, resize, and trigger replacement.

## Trigger Switching

When an active id changes, `usePopupMenu` demotes the previous element to `aria-expanded="false"` and promotes the new trigger. Each trigger must provide the static baseline:

```html
aria-haspopup="menu" aria-expanded="false"
```

The composable does not add `aria-haspopup`, roles, or keyboard focusability.
