# usePopover

## 使用方法

点击触发、带焦点陷阱的浮层内核。它在 `useLayer` 之上叠加了 `useFocusTrap`、自动聚焦、点击外部关闭和 Escape 处理，做出「像对话一样」的无障碍弹出层。

```ts
import { usePopover } from '@astryx-vue/core/Popover'
```

把 `triggerRef` 绑到负责打开浮层的元素上：hook 会给它挂点击和键盘处理，并写好 `aria-haspopup`、`aria-expanded`、`aria-controls`。浮层自己渲染，把 `surfaceProps` 和 `contentStyle` 摊到浮层元素上：

```vue
<script setup lang="ts">
import { usePopover } from '@astryx-vue/core/Popover'

const popover = usePopover({ dialogLabel: 'Quick actions' })
</script>

<template>
  <button :ref="popover.triggerRef" type="button" @click="popover.toggle()">
    Open actions
  </button>
  <Teleport to="body">
    <div
      v-show="popover.isOpen"
      :ref="popover.contentRef"
      v-bind="popover.surfaceProps"
      :style="popover.contentStyle"
    >
      Actions
    </div>
  </Teleport>
</template>
```

`contentStyle` 里是量出来的坐标，浮层需要自己写 `position: fixed`。绘制出来的那层表面就是统一的 `popover` 目标。

## 建议

| 指引 | 做法 |
| --- | --- |
| Do | 菜单、选择器、表单、命令面板这类需要管焦点的交互内容用它。 |
| Do | 标准的「触发器 + 内容」组合优先用 `Popover` 组件；触发器形态特殊时再用 hook。 |
| Do | 把 popover 当作宽泛的统一表面目标。 |
| Do | 自定义组合需要自己的主题细化时，传一个自己的 class（比如 `selector-popup`）并在文档里写明。它细化的是 popover 表面，不是另造一个解剖部件。 |
| Don't | 不要拿它做非交互的悬停预览：那种场景用 `useHoverCard` 或 `useTooltip`。 |

## 示例

用 `usePopover` 搭一个自定义的快速操作弹层，触发器 ref、ARIA 属性和焦点陷阱都由 hook 负责。

### usePopover — Quick Actions

用 usePopover 搭自定义快速操作弹层，触发器 ref、ARIA 属性和焦点陷阱都由 hook 负责。

```vue
<script setup lang="ts">
import { usePopover } from '@astryx-vue/core/Popover'

const popover = usePopover({
  dialogLabel: 'Quick actions',
  closeButtonSelector: '.hook-popover__close',
})
const { isOpen, surfaceProps, contentStyle, toggle, hide } = popover
</script>

<template>
  <div class="hook-popover">
    <button
      :ref="popover.triggerRef"
      type="button"
      class="hook-popover__trigger"
      @click="toggle"
    >
      Open actions
    </button>

    <Teleport to="body">
      <div
        v-show="isOpen"
        :ref="popover.contentRef"
        v-bind="surfaceProps"
        class="hook-popover__surface"
        :style="contentStyle"
      >
        <p class="hook-popover__title">
          Quick actions
        </p>
        <div class="hook-popover__actions">
          <button type="button" class="hook-popover__action">
            Duplicate
          </button>
          <button type="button" class="hook-popover__action">
            Archive
          </button>
        </div>
        <button type="button" class="hook-popover__close" @click="hide">
          Close
        </button>
      </div>
    </Teleport>
  </div>
</template>

<style scoped>
.hook-popover {
  display: flex;
  justify-content: center;
}

.hook-popover__trigger {
  padding: 8px 16px;
  border: 1px solid var(--astryx-color-border, #e5e5e5);
  border-radius: var(--astryx-radius-control, 8px);
  background: var(--astryx-color-background-surface, #fff);
  color: var(--astryx-color-text-primary, #171717);
  font: inherit;
  font-size: 0.875rem;
  font-weight: 600;
  cursor: pointer;
}

.hook-popover__surface {
  position: fixed;
  z-index: 1000;
  display: flex;
  flex-direction: column;
  gap: 12px;
  box-sizing: border-box;
  width: 15rem;
  padding: 12px;
  border: 1px solid var(--astryx-color-border, #e5e5e5);
  border-radius: var(--astryx-radius-container, 12px);
  background: var(--astryx-color-background-surface, #fff);
  box-shadow: 0 4px 8px oklch(0 0 0 / 10%);
  color: var(--astryx-color-text-primary, #171717);
  font-size: 0.875rem;
}

.hook-popover__title {
  margin: 0;
  font-weight: 600;
}

.hook-popover__actions {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.hook-popover__action {
  padding: 6px 8px;
  border: 0;
  border-radius: var(--astryx-radius-control, 8px);
  background: transparent;
  color: inherit;
  font: inherit;
  text-align: start;
  cursor: pointer;
}

.hook-popover__action:hover {
  background: var(--astryx-color-background-muted, #f5f5f5);
}

.hook-popover__close {
  align-self: flex-start;
  padding: 0;
  border: 0;
  background: none;
  color: var(--astryx-color-text-secondary, #525252);
  font: inherit;
  font-size: 0.75rem;
  text-decoration: underline;
  cursor: pointer;
}
</style>
```

## 参数

| 参数 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `placement` | `'above' \| 'below' \| 'start' \| 'end' \| MaybeRefOrGetter` | `'below'` | 浮层落在触发器的哪一侧。逻辑方向：`start` / `end` 在 RTL 下自动镜像。空间不够时翻到对面。 |
| `alignment` | `'start' \| 'center' \| 'end' \| MaybeRefOrGetter` | `'start'` | 沿方位轴的对齐方式。 |
| `isOpen` | `boolean \| undefined \| MaybeRefOrGetter` | — | 受控的打开状态。不传就由 hook 自己管。 |
| `isEnabled` | `boolean \| MaybeRefOrGetter` | `true` | 触发器点击是否打开浮层。 |
| `hasLightDismiss` | `boolean \| MaybeRefOrGetter` | `true` | 点击外部是否关闭。 |
| `hasEscapeDismiss` | `boolean \| MaybeRefOrGetter` | `true` | 按 Escape 是否关闭。 |
| `hasAutoFocus` | `boolean \| MaybeRefOrGetter` | `true` | 打开后是否把焦点移到内容里第一个真正的操作控件。对话框里没有可聚焦元素时，焦点落到内容容器上。 |
| `closeButtonSelector` | `string` | — | 初始焦点要跳过的控件选择器，比如组件自带的关闭按钮。 |
| `dialogLabel` | `string \| MaybeRefOrGetter` | — | 弹出层对话框的无障碍名称。没有可见标题时要传。 |
| `role` | `'dialog' \| 'none' \| MaybeRefOrGetter` | `'dialog'` | 内容容器的 ARIA role。菜单、列表框这类自己带 role、且触发器自己保留焦点的弹层用 `none`。 |
| `isModal` | `boolean \| MaybeRefOrGetter` | `true` | `role="dialog"` 时是否模态（`aria-modal`）。 |
| `onShow` | `() => void` | — | 浮层显示后调用。 |
| `onHide` | `() => void` | — | 浮层隐藏后调用。需要时用它把焦点还给触发器。 |

## 返回值

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `isOpen` | `Ref<boolean>` | 浮层当前是否打开。 |
| `triggerRef` | `Ref` | 负责打开浮层的元素。用 `:ref` 绑上去，hook 会挂事件和 ARIA 属性。 |
| `anchorRef` | `Ref` | 定位用的锚点。没设时退回到 `triggerRef`。 |
| `contentRef` | `Ref` | 浮层内容容器，焦点陷阱和关闭逻辑都靠它。 |
| `id` | `string` | 浮层的唯一 ID，用于 `aria-controls`。 |
| `anchorId` | `string` | 锚点的 CSS 标识，用于高级定位场景。 |
| `contentStyle` | `ComputedRef` | 浮层的内联坐标，摊到内容元素的 `style` 上。 |
| `resolvedPlacement` | `ComputedRef<'above' \| 'below' \| 'start' \| 'end'>` | 考虑空间翻转之后实际生效的方位。 |
| `surfaceProps` | `ComputedRef<Record<string, unknown>>` | 内容元素的属性：`id`、`role`、`aria-modal`、`aria-label`、`tabindex`。 |
| `triggerProps` | `ComputedRef<Record<string, unknown>>` | hook 自动写到 `triggerRef` 上的 ARIA 属性：`aria-haspopup`、`aria-expanded`、`aria-controls`。 |
| `show` | `() => void` | 主动打开浮层。 |
| `hide` | `() => void` | 主动关闭浮层。 |
| `toggle` | `() => void` | 在打开和关闭之间切换。 |
