# usePopupTrap

[← Back to Composables README](https://github.com/NantHealth/featherk/blob/integration/packages/composables/README.md)

Composable for managing focus trapping and close behavior for popup UIs (e.g., Kendo `Popup`, `DatePicker`, `TimePicker` dropdowns). It provides focus trapping via `@vueuse/integrations/useFocusTrap`, Escape/OutsideClick to close, automatic popup element discovery, and optional return-to-trigger focus.

## Prerequisites

- Vue 3 Composition API
- `@featherk/composables` (ships `@vueuse/integrations`, `@vueuse/core`, and `focus-trap` as runtime dependencies)
- Optional: Kendo Vue components (`Popup`, `DatePicker`, `TimePicker`) or any popup-like UI

## Quick Start

```ts
<script setup lang="ts">
import { ref, shallowRef } from "vue";
import { DatePicker } from "@progress/kendo-vue-dateinputs";
import { usePopupTrap } from "@featherk/composables/trap";

const open = ref(false);
const datePickerRef = shallowRef<HTMLElement | null>(null);

usePopupTrap({
  isOpen: open,
  onRequestClose: () => (open.value = false),
  triggerEl: datePickerRef,
  // Use defaults: finds .k-popup near the trigger within .k-animation-container
});
</script>

<template>
  <div ref="datePickerRef">
    <DatePicker v-model="/* your value */" :show="open" @open="open=true" @close="open=false" />
  </div>
</template>
```

### TimePicker example (custom selectors)

```ts
<script setup lang="ts">
import { ref, useTemplateRef } from "vue";
import { TimePicker } from "@progress/kendo-vue-dateinputs";
import { usePopupTrap } from "@featherk/composables/trap";

const showPicker = ref(false);
const timePickerRef = useTemplateRef("timePickerRef");

usePopupTrap({
  isOpen: showPicker,
  onRequestClose: () => (showPicker.value = false),
  triggerEl: timePickerRef,
  popupSelector: [".k-timeselector", ".k-popup"],
  returnFocusToTrigger: false, // parent will refocus the custom input
});
</script>

<template>
  <div ref="timePickerRef">
    <TimePicker :show="showPicker" @open="showPicker=true" @close="showPicker=false" />
  </div>
</template>
```

## API

### `usePopupTrap(options)`

Sets up focus trap and close behaviors for a discovered popup element.

#### Options

- `isOpen: Ref<boolean>`: Reactive flag indicating whether the popup is open.
- `onRequestClose?: (reason: CloseReason, ev?: Event) => void`: Callback to request closing the popup. Reasons: `'escape' | 'outside'`.
- `popupSelector?: string | string[]`: CSS selector(s) used to locate the popup element. Defaults target common Kendo popup containers.
- `triggerEl?: Ref<HTMLElement | null>`: The trigger/root element used as a scope to discover the popup.
- `resolvePopupEl?: () => HTMLElement | null`: Provide your own resolver when direct selection is needed.
- `initialFocus?: InitialFocus`: The initial focus target inside the popup: a CSS selector string or function `(root) => HTMLElement | null`.
- `focusTrapOptions?: Parameters<typeof useFocusTrap>[1]`: Options forwarded to `useFocusTrap`.
- `returnFocusToTrigger?: boolean`: When closing, return focus to `triggerEl`. Defaults to `true`.

#### Returns

- `popupRef: Ref<HTMLElement | null>`: The active popup element.
- `activate(): void`: Manually activate the focus trap.
- `deactivate(): void`: Manually deactivate the focus trap.
- `setPopupEl(el: HTMLElement | null): void`: Override the discovered popup element.

#### Types

```ts
export type CloseReason = "escape" | "outside";
export type InitialFocus = string | ((root: HTMLElement) => HTMLElement | null | undefined);
```

## Behavior Details

- **Discovery**: Attempts to find the popup near `triggerEl` within its closest `.k-animation-container` or `document.body`. Default selectors:
  - `.k-animation-container .k-popup`
  - `.k-popup`
  - `.k-timepicker-popup`
  - `.k-menu-popup`
- **Focus trap**: Uses `useFocusTrap(popupRef)` with a fallback/initial focus inside the popup. `escapeDeactivates` and `clickOutsideDeactivates` are disabled; closing is managed explicitly.
- **Escape to close**: Adds a `keydown` listener on the popup when opened; pressing Escape calls `onRequestClose('escape', event)` and stops propagation.
- **Outside click to close**: Uses `onClickOutside(popupRef, ...)` to call `onRequestClose('outside', event)` when open.
- **Open/close lifecycle**: When `isOpen` becomes true, discovers the popup, sets `popupRef`, and activates the focus trap. When it becomes false, deactivates, removes listeners, clears `popupRef`, and optionally returns focus to `triggerEl`.

## Integration Patterns

- **Kendo DatePicker/Popup**: Use defaults; pass `triggerEl` pointing to the DatePicker wrapper and `isOpen` from the component’s open state.
- **Kendo TimePicker**: Provide `popupSelector` like `[".k-timeselector", ".k-popup"]` to target the menu container reliably.
  - Note: Kendo Vue `TimePicker` uses the `dateInput` slot/prop to replace its internal input (same as `DatePicker`). Use `dateInput="masked"` and define the `#masked` slot; `timeInput` will not work.
- **Manual resolution**: When a popup is outside the default scope, set `resolvePopupEl` to return the element directly.
- **Initial focus**: Use `initialFocus` to direct focus to a primary interactive element inside the popup (e.g., first calendar cell or button).

## Accessibility Notes

- Trap focus while the popup is open and ensure Escape closes it.
- Return focus to the trigger after closing (or handle focus yourself by setting `returnFocusToTrigger: false`).
- Provide an accessible label for the trigger and ensure tab order is logical.

## Limitations & Assumptions

- Discovery relies on stable CSS selectors; customize `popupSelector` or use `resolvePopupEl` if your UI differs.
- Focus guard behavior is tuned for Kendo popups; other libraries may require selector adjustments.

## Tips

- Keep `onRequestClose` idempotent; it can be called by Escape and outside click.
- When combining with masked inputs, consider focusing the custom input after closing (disable `returnFocusToTrigger` and handle focus yourself).
- If multiple popups can be open, ensure selectors narrow to the correct one or provide `resolvePopupEl`.

## Reference Implementations

- Date example: [src/components/custom-date-picker/CustomDatePicker.vue](../src/components/custom-date-picker/CustomDatePicker.vue)
- Time example: [src/components/custom-time-picker/CustomTimePicker.vue](../src/components/custom-time-picker/CustomTimePicker.vue)
