# useHoverCard

## 使用方法

悬停触发的浮层卡片。它在 `useLayer` 之上加了悬停 / 焦点意图识别、可配置延迟、安全的悬停行为，以及 `aria-describedby` 关联。需要完全掌控触发器或卡片内容时用它。

```ts
import { useHoverCard } from '@astryx-vue/core/HoverCard'
```

把 `triggerRef` 绑到要弹卡片的元素上：hook 会挂指针、焦点和触摸处理，并让指针能移进卡片而不中途关闭。卡片自己渲染，把 `contentProps` 摊上去：

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

const card = useHoverCard({ label: 'Alex Morgan' })
</script>

<template>
  <button :ref="card.triggerRef" type="button">
    Hover profile
  </button>
  <Teleport to="body">
    <div
      v-show="card.isOpen"
      :ref="card.contentRef"
      v-bind="card.contentProps"
      :style="card.contentStyle"
    >
      Alex Morgan
    </div>
  </Teleport>
</template>
```

给了 `label`，卡片就是有名字的 `role="dialog"`，触发器用 `aria-haspopup` / `aria-controls` / `aria-expanded` 宣告它；不给就退回 `role="group"`，走 `aria-describedby`。`contentProps` 里带着 role、label，以及保持卡片打开的处理函数。

## 建议

| 指引 | 做法 |
| --- | --- |
| Do | 用户资料、实体摘要、链接预览这类富内容预览用它。 |
| Do | 标准的「触发器 + 内容」组合优先用 `HoverCard` 组件；触发器形态特殊时再用 hook。 |
| Don't | 简单文字提示不要用它：那种场景用 `Tooltip` 或 `useTooltip`。 |

## 示例

用 `useHoverCard` 搭一个自定义的资料卡预览，触发器和卡片内容都自己掌控。

### useHoverCard — Profile Preview

用 useHoverCard 搭自定义资料卡预览，触发器和内容渲染都自己掌控。

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

const card = useHoverCard({ label: 'Alex Morgan', delay: 150 })
const { isOpen, contentStyle, contentProps } = card
</script>

<template>
  <div class="hook-hover-card">
    <button :ref="card.triggerRef" type="button" class="hook-hover-card__trigger">
      Hover profile
    </button>

    <Teleport to="body">
      <div
        v-show="isOpen"
        :ref="card.contentRef"
        v-bind="contentProps"
        class="hook-hover-card__surface"
        :style="contentStyle"
      >
        <p class="hook-hover-card__name">
          Alex Morgan
        </p>
        <p class="hook-hover-card__role">
          Staff designer · Product systems
        </p>
        <p class="hook-hover-card__bio">
          Owns interaction patterns for overlays and navigation.
        </p>
      </div>
    </Teleport>
  </div>
</template>

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

.hook-hover-card__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-hover-card__surface {
  position: fixed;
  z-index: 1000;
  box-sizing: border-box;
  width: 16rem;
  padding: 14px;
  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-hover-card__name {
  margin: 0;
  font-weight: 600;
}

.hook-hover-card__role {
  margin: 2px 0 8px;
  color: var(--astryx-color-text-secondary, #525252);
  font-size: 0.75rem;
}

.hook-hover-card__bio {
  margin: 0;
  line-height: 1.5;
}
</style>
```

## 参数

| 参数 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `placement` | `'above' \| 'below' \| 'start' \| 'end' \| MaybeRefOrGetter` | `'above'` | 卡片落在触发器的哪一侧。逻辑方向：`start` / `end` 在 RTL 下自动镜像。空间不够时翻到对面。 |
| `alignment` | `'start' \| 'center' \| 'end' \| MaybeRefOrGetter` | `'center'` | 沿方位轴的对齐方式。 |
| `delay` | `number \| MaybeRefOrGetter` | `300` | 悬停多久才显示卡片，单位毫秒。 |
| `hideDelay` | `number \| MaybeRefOrGetter` | `200` | 指针或焦点离开后多久隐藏，单位毫秒。指针可以借这段时间移进卡片。 |
| `focusTrigger` | `'auto' \| 'always' \| 'never' \| MaybeRefOrGetter` | `'auto'` | 焦点何时打开卡片。`auto` 只给本来就能聚焦的元素挂焦点监听。 |
| `touchTrigger` | `'auto' \| 'tap' \| 'none' \| MaybeRefOrGetter` | `'auto'` | 没有 hover 的触摸设备上轻点做什么。`auto` 在触发器自己有动作时不打开；`tap` 总是打开；`none` 从不打开。 |
| `isEnabled` | `boolean \| MaybeRefOrGetter` | `true` | 是否启用悬停与焦点触发。 |
| `label` | `string \| undefined \| MaybeRefOrGetter` | — | 卡片的无障碍名称。给了就以有名字的 `role="dialog"` 暴露；不给退回 `role="group"`（组可以没有名字）。 |
| `isOpen` | `boolean \| undefined \| MaybeRefOrGetter` | — | 受控的打开状态。`true` 强制显示，`false` 强制隐藏，`undefined` 交给悬停/焦点管理。 |
| `isDefaultOpen` | `boolean` | `false` | 挂载时是否显示。 |
| `onShow` | `() => void` | — | 卡片显示后调用。 |
| `onHide` | `() => void` | — | 卡片隐藏后调用。 |

## 返回值

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `isOpen` | `Ref<boolean>` | 卡片当前是否显示。 |
| `triggerRef` | `Ref` | 触发器元素。用 `:ref` 绑上去，hook 会挂事件和 ARIA 关系。 |
| `contentRef` | `Ref` | 卡片容器。 |
| `id` | `string` | 卡片的唯一 ID。 |
| `anchorId` | `string` | 锚点的 CSS 标识，用于高级定位场景。 |
| `describedBy` | `string` | 要拼进 `aria-describedby` 的 ID。hook 已经写到 `triggerRef` 上了，手写 ARIA 时才需要它。 |
| `contentStyle` | `ComputedRef` | 卡片的内联坐标。 |
| `resolvedPlacement` | `ComputedRef<'above' \| 'below' \| 'start' \| 'end'>` | 考虑空间翻转之后实际生效的方位。 |
| `isDialog` | `ComputedRef<boolean>` | 卡片是以有名字的对话暴露，还是无名分组。 |
| `contentProps` | `ComputedRef<Record<string, unknown>>` | 卡片的属性：`id`、`role`、`aria-label`，以及保持显示的指针 / 焦点处理。 |
| `show` | `() => void` | 立刻显示卡片。 |
| `hide` | `() => void` | 立刻隐藏卡片。 |
