来源：https://ui.docs.xihanfun.com/components/tooltip

# Tooltip 文字提示

悬停或聚焦时出现的一句纯文字说明。

<div class="xh-resource-links">
  <a href="https://github.com/XiHanFun/XiHan.UI/tree/dev/ui/packages/engine/headless/src/tooltip" target="_blank" rel="noreferrer">Headless</a>
  <a href="https://github.com/XiHanFun/XiHan.UI/blob/dev/ui/packages/design/styles/css/tooltip.css" target="_blank" rel="noreferrer">Styles</a>
  <a href="https://github.com/XiHanFun/XiHan.UI/tree/dev/ui/packages/adapters/vue/src/components/tooltip" target="_blank" rel="noreferrer">Vue</a>
  <a href="https://github.com/XiHanFun/XiHan.UI/tree/dev/ui/packages/adapters/react/src/components/tooltip" target="_blank" rel="noreferrer">React</a>
  <a href="https://github.com/XiHanFun/XiHan.UI/blob/dev/ui/packages/adapters/web-components/src/elements/tooltip.ts" target="_blank" rel="noreferrer">Web Components</a>
</div>

## 用法

悬停或聚焦触发器即显示；指针停在提示上也不收起

```vue
<script setup lang="ts">
import {
  XhTooltipArrow,
  XhTooltipContent,
  XhTooltipPositioner,
  XhTooltipRoot,
  XhTooltipTrigger,
} from "@xihan-ui/vue";
</script>

<template>
  <XhTooltipRoot>
    <XhTooltipTrigger>保存</XhTooltipTrigger>
    <XhTooltipPositioner>
      <XhTooltipContent>
        写入草稿箱，不会发布
        <XhTooltipArrow />
      </XhTooltipContent>
    </XhTooltipPositioner>
  </XhTooltipRoot>
</template>
```

```html
<xh-tooltip>
  <button data-xh-part="trigger">保存</button>
  <div data-xh-part="positioner">
    <div data-xh-part="content">
      写入草稿箱，不会发布
      <div data-xh-part="arrow"></div>
    </div>
  </div>
</xh-tooltip>
```

## 组件结构

加粗的是必需部件。

`data-scope="tooltip"`：**`trigger`** · `positioner` · **`content`** · `arrow`

## 示例

### 朝向

placement 是请求值，空间不足时由定位引擎避让；箭头跟随最终落定的一面

```vue
<script setup lang="ts">
import {
  XhTooltipArrow,
  XhTooltipContent,
  XhTooltipPositioner,
  XhTooltipRoot,
  XhTooltipTrigger,
} from "@xihan-ui/vue";

const placements = [
  { value: "top", label: "上方" },
  { value: "right", label: "右侧" },
  { value: "bottom", label: "下方" },
  { value: "left", label: "左侧" },
] as const;
</script>

<template>
  <div style="display: flex; flex-wrap: wrap; gap: 24px">
    <XhTooltipRoot
      v-for="p in placements"
      :key="p.value"
      :placement="p.value"
      :open-delay="0"
    >
      <XhTooltipTrigger>{{ p.label }}</XhTooltipTrigger>
      <XhTooltipPositioner>
        <XhTooltipContent>
          请求朝向 {{ p.value }}
          <XhTooltipArrow />
        </XhTooltipContent>
      </XhTooltipPositioner>
    </XhTooltipRoot>
  </div>
</template>
```

```html
<div style="display: flex; flex-wrap: wrap; gap: 24px">
  <xh-tooltip placement="top" open-delay="0">
    <button data-xh-part="trigger">上方</button>
    <div data-xh-part="positioner">
      <div data-xh-part="content">
        请求朝向 top
        <div data-xh-part="arrow"></div>
      </div>
    </div>
  </xh-tooltip>

  <xh-tooltip placement="right" open-delay="0">
    <button data-xh-part="trigger">右侧</button>
    <div data-xh-part="positioner">
      <div data-xh-part="content">
        请求朝向 right
        <div data-xh-part="arrow"></div>
      </div>
    </div>
  </xh-tooltip>

  <xh-tooltip placement="bottom" open-delay="0">
    <button data-xh-part="trigger">下方</button>
    <div data-xh-part="positioner">
      <div data-xh-part="content">
        请求朝向 bottom
        <div data-xh-part="arrow"></div>
      </div>
    </div>
  </xh-tooltip>

  <xh-tooltip placement="left" open-delay="0">
    <button data-xh-part="trigger">左侧</button>
    <div data-xh-part="positioner">
      <div data-xh-part="content">
        请求朝向 left
        <div data-xh-part="arrow"></div>
      </div>
    </div>
  </xh-tooltip>
</div>
```

### 延时

openDelay 默认 700ms 用于防误触，closeDelay 默认 300ms 留出指针移动的余地；聚焦不经这两段等待

```vue
<script setup lang="ts">
import {
  XhTooltipContent,
  XhTooltipPositioner,
  XhTooltipRoot,
  XhTooltipTrigger,
} from "@xihan-ui/vue";
</script>

<template>
  <div style="display: flex; flex-wrap: wrap; gap: 24px">
    <XhTooltipRoot>
      <XhTooltipTrigger>默认（700 / 300）</XhTooltipTrigger>
      <XhTooltipPositioner>
        <XhTooltipContent>停够 700ms 才出来</XhTooltipContent>
      </XhTooltipPositioner>
    </XhTooltipRoot>

    <XhTooltipRoot :open-delay="0" :close-delay="0">
      <XhTooltipTrigger>无延时</XhTooltipTrigger>
      <XhTooltipPositioner>
        <XhTooltipContent>指针一进就出，一走就收</XhTooltipContent>
      </XhTooltipPositioner>
    </XhTooltipRoot>

    <XhTooltipRoot :open-delay="1500">
      <XhTooltipTrigger>慢一点（1500）</XhTooltipTrigger>
      <XhTooltipPositioner>
        <XhTooltipContent>用 Tab 聚焦它，立刻就出来</XhTooltipContent>
      </XhTooltipPositioner>
    </XhTooltipRoot>
  </div>
</template>
```

```html
<div style="display: flex; flex-wrap: wrap; gap: 24px">
  <xh-tooltip>
    <button data-xh-part="trigger">默认（700 / 300）</button>
    <div data-xh-part="positioner">
      <div data-xh-part="content">停够 700ms 才出来</div>
    </div>
  </xh-tooltip>

  <xh-tooltip open-delay="0" close-delay="0">
    <button data-xh-part="trigger">无延时</button>
    <div data-xh-part="positioner">
      <div data-xh-part="content">指针一进就出，一走就收</div>
    </div>
  </xh-tooltip>

  <xh-tooltip open-delay="1500">
    <button data-xh-part="trigger">慢一点（1500）</button>
    <div data-xh-part="positioner">
      <div data-xh-part="content">用 Tab 聚焦它，立刻就出来</div>
    </div>
  </xh-tooltip>
</div>
```

### 禁用

disabled 只关闭提示本身，被包裹的触发器照常可点击、可聚焦

```vue
<script setup lang="ts">
import {
  XhTooltipContent,
  XhTooltipPositioner,
  XhTooltipRoot,
  XhTooltipTrigger,
} from "@xihan-ui/vue";
import { ref } from "vue";

const clicks = ref(0);
</script>

<template>
  <div style="display: flex; align-items: center; gap: 16px">
    <XhTooltipRoot disabled>
      <XhTooltipTrigger @click="clicks++">点我（提示已关）</XhTooltipTrigger>
      <XhTooltipPositioner>
        <XhTooltipContent>这段话不会出现</XhTooltipContent>
      </XhTooltipPositioner>
    </XhTooltipRoot>
    <span>已点 {{ clicks }} 次</span>
  </div>
</template>
```

```html
<div style="display: flex; align-items: center; gap: 16px">
  <xh-tooltip id="tooltip-disabled" disabled>
    <button data-xh-part="trigger">点我（提示已关）</button>
    <div data-xh-part="positioner">
      <div data-xh-part="content">这段话不会出现</div>
    </div>
  </xh-tooltip>
  <span>已点 <span id="tooltip-disabled-clicks">0</span> 次</span>
</div>

<script type="module">
  // 触发器的点击照常到达，计数跟着走
  const tooltip = document.getElementById("tooltip-disabled");
  const trigger = tooltip.querySelector('[data-xh-part="trigger"]');
  const readout = document.getElementById("tooltip-disabled-clicks");
  let clicks = 0;
  trigger.addEventListener("click", () => {
    clicks += 1;
    readout.textContent = String(clicks);
  });
</script>
```

### 颜色

六种语气更换浮层实心底与其上的文字色，箭头一并随之变化；把指针停在触发器上（或用 Tab 聚焦）查看差别

```vue
<script setup lang="ts">
import {
  XhTooltipArrow,
  XhTooltipContent,
  XhTooltipPositioner,
  XhTooltipRoot,
  XhTooltipTrigger,
} from "@xihan-ui/vue";

const tones = [
  { value: "brand", label: "品牌" },
  { value: "neutral", label: "中性" },
  { value: "success", label: "成功" },
  { value: "warning", label: "警告" },
  { value: "danger", label: "危险" },
  { value: "info", label: "信息" },
] as const;
</script>

<template>
  <div style="display: flex; flex-wrap: wrap; gap: 24px">
    <XhTooltipRoot
      v-for="t in tones"
      :key="t.value"
      :tone="t.value"
      placement="bottom"
      :open-delay="0"
    >
      <XhTooltipTrigger>{{ t.label }}</XhTooltipTrigger>
      <XhTooltipPositioner>
        <XhTooltipContent>
          tone = {{ t.value }}
          <XhTooltipArrow />
        </XhTooltipContent>
      </XhTooltipPositioner>
    </XhTooltipRoot>
  </div>
</template>
```

```html
<div style="display: flex; flex-wrap: wrap; gap: 24px">
  <xh-tooltip tone="brand" placement="bottom" open-delay="0">
    <button data-xh-part="trigger">品牌</button>
    <div data-xh-part="positioner">
      <div data-xh-part="content">
        tone = brand
        <div data-xh-part="arrow"></div>
      </div>
    </div>
  </xh-tooltip>

  <xh-tooltip tone="neutral" placement="bottom" open-delay="0">
    <button data-xh-part="trigger">中性</button>
    <div data-xh-part="positioner">
      <div data-xh-part="content">
        tone = neutral
        <div data-xh-part="arrow"></div>
      </div>
    </div>
  </xh-tooltip>

  <xh-tooltip tone="success" placement="bottom" open-delay="0">
    <button data-xh-part="trigger">成功</button>
    <div data-xh-part="positioner">
      <div data-xh-part="content">
        tone = success
        <div data-xh-part="arrow"></div>
      </div>
    </div>
  </xh-tooltip>

  <xh-tooltip tone="warning" placement="bottom" open-delay="0">
    <button data-xh-part="trigger">警告</button>
    <div data-xh-part="positioner">
      <div data-xh-part="content">
        tone = warning
        <div data-xh-part="arrow"></div>
      </div>
    </div>
  </xh-tooltip>

  <xh-tooltip tone="danger" placement="bottom" open-delay="0">
    <button data-xh-part="trigger">危险</button>
    <div data-xh-part="positioner">
      <div data-xh-part="content">
        tone = danger
        <div data-xh-part="arrow"></div>
      </div>
    </div>
  </xh-tooltip>

  <xh-tooltip tone="info" placement="bottom" open-delay="0">
    <button data-xh-part="trigger">信息</button>
    <div data-xh-part="positioner">
      <div data-xh-part="content">
        tone = info
        <div data-xh-part="arrow"></div>
      </div>
    </div>
  </xh-tooltip>
</div>
```

### 尺寸

三档改变浮层的内边距与字号，不写 size 即默认档；把指针停在触发器上（或用 Tab 聚焦）查看差别

```vue
<script setup lang="ts">
import {
  XhTooltipArrow,
  XhTooltipContent,
  XhTooltipPositioner,
  XhTooltipRoot,
  XhTooltipTrigger,
} from "@xihan-ui/vue";

const sizes = [
  { value: "sm", label: "小" },
  { value: undefined, label: "缺省" },
  { value: "lg", label: "大" },
] as const;
</script>

<template>
  <div style="display: flex; flex-wrap: wrap; gap: 24px">
    <XhTooltipRoot
      v-for="s in sizes"
      :key="s.label"
      :size="s.value"
      placement="bottom"
      :open-delay="0"
    >
      <XhTooltipTrigger>{{ s.label }}</XhTooltipTrigger>
      <XhTooltipPositioner>
        <XhTooltipContent>
          size = {{ s.value ?? "未指定" }}
          <XhTooltipArrow />
        </XhTooltipContent>
      </XhTooltipPositioner>
    </XhTooltipRoot>
  </div>
</template>
```

```html
<div style="display: flex; flex-wrap: wrap; gap: 24px">
  <xh-tooltip size="sm" placement="bottom" open-delay="0">
    <button data-xh-part="trigger">小</button>
    <div data-xh-part="positioner">
      <div data-xh-part="content">
        size = sm
        <div data-xh-part="arrow"></div>
      </div>
    </div>
  </xh-tooltip>

  <xh-tooltip placement="bottom" open-delay="0">
    <button data-xh-part="trigger">缺省</button>
    <div data-xh-part="positioner">
      <div data-xh-part="content">
        size = 未指定
        <div data-xh-part="arrow"></div>
      </div>
    </div>
  </xh-tooltip>

  <xh-tooltip size="lg" placement="bottom" open-delay="0">
    <button data-xh-part="trigger">大</button>
    <div data-xh-part="positioner">
      <div data-xh-part="content">
        size = lg
        <div data-xh-part="arrow"></div>
      </div>
    </div>
  </xh-tooltip>
</div>
```

### 受控

传入 open 后由宿主决定；悬停、聚焦、Escape 都只发意图，最终是否写回由外部的这份状态决定

```vue
<script setup lang="ts">
import {
  XhButton,
  XhTooltipArrow,
  XhTooltipContent,
  XhTooltipPositioner,
  XhTooltipRoot,
  XhTooltipTrigger,
} from "@xihan-ui/vue";
import { ref } from "vue";

const open = ref(false);
const log = ref<string[]>([]);

// 只留最近三条意图
function onOpenChange(details: { open: boolean }) {
  log.value = [details.open ? "要展开" : "要收起", ...log.value].slice(0, 3);
}
</script>

<template>
  <div style="display: flex; align-items: center; gap: 16px">
    <XhTooltipRoot
      v-model:open="open"
      placement="bottom"
      :open-delay="0"
      @open-change="onOpenChange"
    >
      <XhTooltipTrigger>把指针停上来</XhTooltipTrigger>
      <XhTooltipPositioner>
        <XhTooltipContent>
          显隐完全跟着 open 走
          <XhTooltipArrow />
        </XhTooltipContent>
      </XhTooltipPositioner>
    </XhTooltipRoot>

    <XhButton variant="outline" @click="open = !open">
      {{ open ? "收起" : "展开" }}
    </XhButton>
    <span>最近意图：{{ log.join(" ← ") || "（还没动过）" }}</span>
  </div>
</template>
```

```html
<div style="display: flex; align-items: center; gap: 16px">
  <xh-tooltip id="tooltip-controlled" open="false" placement="bottom" open-delay="0">
    <button data-xh-part="trigger">把指针停上来</button>
    <div data-xh-part="positioner">
      <div data-xh-part="content">
        显隐完全跟着 open 走
        <div data-xh-part="arrow"></div>
      </div>
    </div>
  </xh-tooltip>

  <xh-button variant="outline">
    <button data-xh-part="root" id="tooltip-controlled-toggle">展开</button>
  </xh-button>
  <span>最近意图：<span id="tooltip-controlled-log">（还没动过）</span></span>
</div>

<script type="module">
  // 开合由外面这份状态说了算，组件报上来的意图先记一笔再写回去
  const tooltip = document.getElementById("tooltip-controlled");
  const toggle = document.getElementById("tooltip-controlled-toggle");
  const readout = document.getElementById("tooltip-controlled-log");
  let log = [];

  function apply(open) {
    tooltip.open = open;
    toggle.textContent = open ? "收起" : "展开";
  }

  tooltip.addEventListener("open-change", (event) => {
    // 只留最近三条意图
    log = [event.detail.open ? "要展开" : "要收起", ...log].slice(0, 3);
    readout.textContent = log.join(" ← ");
    apply(event.detail.open);
  });
  toggle.addEventListener("click", () => apply(!tooltip.open));
</script>
```

### 长文案

提示到达宽度上限后换行，不会拉成一条横线；上限是 content 上的 --xh-tooltip-max-w 槽位

```vue
<script setup lang="ts">
import {
  XhTooltipArrow,
  XhTooltipContent,
  XhTooltipPositioner,
  XhTooltipRoot,
  XhTooltipTrigger,
} from "@xihan-ui/vue";

const text = "导出会把当前筛选条件下的全部行写进文件，行数很多时要等上一会儿。";
</script>

<template>
  <div style="display: flex; flex-wrap: wrap; gap: 24px">
    <XhTooltipRoot placement="bottom" :open-delay="0">
      <XhTooltipTrigger>缺省上限</XhTooltipTrigger>
      <XhTooltipPositioner>
        <XhTooltipContent>
          {{ text }}
          <XhTooltipArrow />
        </XhTooltipContent>
      </XhTooltipPositioner>
    </XhTooltipRoot>

    <XhTooltipRoot placement="bottom" :open-delay="0">
      <XhTooltipTrigger>放宽到 360px</XhTooltipTrigger>
      <XhTooltipPositioner>
        <XhTooltipContent style="--xh-tooltip-max-w: 360px">
          {{ text }}
          <XhTooltipArrow />
        </XhTooltipContent>
      </XhTooltipPositioner>
    </XhTooltipRoot>
  </div>
</template>
```

```html
<div style="display: flex; flex-wrap: wrap; gap: 24px">
  <xh-tooltip placement="bottom" open-delay="0">
    <button data-xh-part="trigger">缺省上限</button>
    <div data-xh-part="positioner">
      <div data-xh-part="content">
        导出会把当前筛选条件下的全部行写进文件，行数很多时要等上一会儿。
        <div data-xh-part="arrow"></div>
      </div>
    </div>
  </xh-tooltip>

  <xh-tooltip placement="bottom" open-delay="0">
    <button data-xh-part="trigger">放宽到 360px</button>
    <div data-xh-part="positioner">
      <div data-xh-part="content" style="--xh-tooltip-max-w: 360px">
        导出会把当前筛选条件下的全部行写进文件，行数很多时要等上一会儿。
        <div data-xh-part="arrow"></div>
      </div>
    </div>
  </xh-tooltip>
</div>
```

## 设计指引

### 何时使用

- 补充说明图标按钮的含义，或截断文字的全文。
- 内容是纯文字，且没有任何可交互元素。

### 何时不用

- 内容中有按钮或链接时，使用[气泡卡片](./popover)，提示无法交互。
- 信息重要到不能错过时，写在界面上，不放进悬停。
- 触摸设备是主要场景时，没有悬停。

### 特性

- `openDelay` / `closeDelay` 防止指针经过时连续闪烁。
- 聚焦也能触发，键盘用户可以访问。
- 语气与尺寸两轴。
- 默认保持反白的小型 M2 表面（compact 档 frosted），与承载操作的 Popover 分开；六种语气都使用高遮蔽 tint 与不透明文字，箭头和气泡同色同边。边界由 on 色 20% 的拼色描边承担（frosted 的透明深边压在反白底上看不见），不画顶部高光；圆角取 4px 控件档。
- 进退场只做侧向短移与透明度，不缩放文字和箭头：入场 `--xh-motion-duration-enter`（200ms），退场 `--xh-motion-duration-exit`（120ms），与其他锚定列表浮层同一节奏。

### 组合

- 挂在[图标](./icon)按钮、[切换按钮](./toggle)、[文本截断](./truncate)上。

### 最佳实践

- 一句话说完，超过一行应换其他形式。
- 必须显示较长的单句时让它在最大宽度内换行；连续长词也会断行，不会把浮层撑出窄屏。
- 图标按钮的可访问名称写在按钮上（`aria-label`），提示只是视觉补充。

### 当前边界

- 当前尚无 TooltipProvider，多个目标间的统一 delay、skip-delay、同组互斥与触发器滚动关闭仍是后续独立行为功能；本次不以样式模拟这些时序。
- 共享浮层位移原语当前最小档是 4px；Tooltip 先与 Menu 使用同一 `xh-overlay-slide-in/out` 定义。规格中的 2px 需要新增公共 motion distance 档后统一接入，不能局部改写现有语义令牌。

### 反模式

- 把唯一的操作说明放进提示，触摸用户无法看到。
- 提示内放链接。

## API 参考

### 产物

| 层 | 值 |
| --- | --- |
| 自定义元素 | `<xh-tooltip>` |
| Vue 组件 | `XhTooltipArrow` `XhTooltipContent` `XhTooltipPositioner` `XhTooltipRoot` `XhTooltipTrigger` |
| 组合式函数 | `useTooltip` |
| 状态机 | `tooltipMachine` |
| 皮肤 | `@xihan-ui/styles/tooltip.css` |

### Props

| 属性 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `open` | `boolean` |  |  |
| `defaultOpen` | `boolean` |  |  |
| `placement` | `Placement` |  | 请求的浮层朝向，默认 bottom；空间不足时由定位引擎避让。 |
| `dir` | `Direction` |  | 文字方向，默认 ltr。只改写浮层在行内轴上 start 与 end 的落点。 |
| `offset` | `number` |  | 浮层与锚点的间距（px）。 |
| `openDelay` | `number` |  | 悬停进入到展开的等待毫秒，默认 700。 |
| `closeDelay` | `number` |  | 悬停移出到收起的等待毫秒，默认 300。 |
| `disabled` | `boolean` |  | 只关闭提示本身，不影响被包裹控件的可用性。 |
| `tone` | `Tone` |  | 语气：brand / neutral / success / warning / danger / info，决定提示的底色与其上的文字色。 |
| `size` | `Size` |  | 尺寸：sm / md / lg，决定内边距与字号档位。 |
| `onOpenChange` | `(details: TooltipOpenChangeDetails) => void` |  | open 变化意图回调；受控时是唯一出口，非受控时随内部转移一并通知。 |

### 事件

自定义元素将载荷放在 `detail`；Vue 使用同名 emit。

| 事件 | 载荷 | 说明 |
| --- | --- | --- |
| `open-change` | `TooltipOpenChangeDetails` | open 状态变化；detail 为 `{ open: boolean }` |

### 插槽

仅列出带载荷的插槽。

| Vue 组件 | 插槽 | 载荷 | 说明 |
| --- | --- | --- | --- |
| `XhTooltipRoot` | `default` | `TooltipRootSlotProps` |  |

### React 适配器 props

只列各组件自己声明的那些：继承自 `ComponentPropsWithRef` 的 DOM 属性不在其中，根组件上与上面 Props 表同名的也不重复列。Vue 的对应物是上面的插槽表。

| React 组件 | 属性 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- | --- |
| `XhTooltipPositioner` | `container` | `() => Element \| null` |  | 浮层挂载的容器；未提供时按全局配置，再未提供时挂载到 body。 |
| `XhTooltipRoot` | `children` | `SlotChildren<TooltipRootSlotProps>` |  |  |

### 状态

公开状态写入 `data-state`。

| 部件 | 取值 |
| --- | --- |
| `trigger` | 'open' \| 'closed' |
| `positioner` | 'open' \| 'closed' |
| `content` | 'open' \| 'closed' |

以下名称仅用于内部状态机。

**状态**：`closed` · `opening` · `visible` · `visible.open` · `visible.closing`

**事件**：`POINTER.ENTER` · `POINTER.LEAVE` · `POINTER.DOWN` · `FOCUS` · `BLUR` · `ESCAPE` · `OPEN` · `CLOSE` · `after.openDelay` · `after.closeDelay` · `CONTROLLED.OPEN` · `CONTROLLED.CLOSE`

**判据**：`isOpenControlled` · `isDisabled` · `isFocusOpened`

### connect API

`getXxxProps()` 返回对应部件的宿主属性。

| 成员 | 类型 | 说明 |
| --- | --- | --- |
| `open` | `boolean` |  |
| `setOpen` | `(next: boolean) => void` |  |
| `getTriggerProps` | `() => T['button']` |  |
| `getPositionerProps` | `() => T['element']` |  |
| `getContentProps` | `() => T['element']` |  |
| `getArrowProps` | `() => T['element']` |  |

## 无障碍

### 键盘

规格出处：[W3C APG](https://www.w3.org/WAI/ARIA/apg/patterns/tooltip/#keyboardinteraction)

| 按键 | 生效条件 | 行为 |
| --- | --- | --- |
| `Tab` / `Shift+Tab` | not disabled | 焦点进入 trigger 立即展开、离开立即收起，都不走延时 |
| `Escape` | 展开中且本层在层栈栈顶，或 focus in trigger 且等待展开中 | 立即收起，不等 closeDelay；下层浮层不受这一次按键影响 |

### ARIA

以下属性由 `connect` 生成。

| 部件 | 属性 | 值 |
| --- | --- | --- |
| `trigger` | `aria-describedby` | `content` 部件的 id \| undefined |
| `content` | `aria-hidden` | !open \|\| undefined |
| `content` | `role` | 'tooltip' |
| `arrow` | `aria-hidden` | 'true' |

## 样式参考

### 皮肤

`@xihan-ui/styles/tooltip.css` 使用 `[data-scope="tooltip"][data-part="trigger"]` 部件选择器，位于 `xihan.components` 层。覆盖样式使用 `xihan.overrides`。

`forced-colors: active` 下另有一套规则：颜色交给系统，边框与状态标记改用系统色关键字。

### 数据属性

由 `connect` 生成；条件不成立时不输出无值属性。

| 部件 | 属性 | 值 |
| --- | --- | --- |
| `trigger` | `data-disabled` | ''（条件成立时才出现） |
| `trigger` | `data-state` | 'open' \| 'closed' |
| `positioner` | `data-hidden` | ''（条件成立时才出现） |
| `positioner` | `data-placement` | 定位引擎算出的实际落位 |
| `positioner` | `data-positioned` | ''（条件成立时才出现） |
| `positioner` | `data-state` | 'open' \| 'closed' |
| `content` | `data-size` | props.size |
| `content` | `data-state` | 'open' \| 'closed' |
| `content` | `data-tone` | props.tone |
| `content` | `data-xh-ink-surface` | '' |
| `arrow` | `data-placement` | 定位引擎算出的实际落位 |

<!-- xh-component-tokens:start -->
### CSS 变量

本组件公开覆盖槽由独立皮肤的实际消费位生成；默认来源、作用部件和状态均与 CSS 同源。

| 变量 | 部件 | CSS 属性 | 状态 | 默认来源 | 说明 |
| --- | --- | --- | --- | --- | --- |
| `--xh-tooltip-arrow-size` | `arrow` | `--xh-_overlay-arrow-size` | `default` | `--xh-overlay-arrow-size` | tooltip 的 arrow 部件 --xh-_overlay-arrow-size 覆盖槽。 |
| `--xh-tooltip-backdrop` | `content` | `-webkit-backdrop-filter`<br>`backdrop-filter` | `default` | `--xh-material-frosted-compact-backdrop` | tooltip 的 content 部件 -webkit-backdrop-filter、backdrop-filter 覆盖槽。 |
| `--xh-tooltip-bg` | `arrow`<br>`content` | `--xh-ink-surface`<br>`background` | `default`<br>`xh-ink-surface` | `--xh-_tooltip-solid` | tooltip 的 arrow、content 部件 --xh-ink-surface、background 覆盖槽。 |
| `--xh-tooltip-border` | `arrow`<br>`content` | `border` | `default` | `--xh-_tooltip-border` | tooltip 的 arrow、content 部件 border 覆盖槽。 |
| `--xh-tooltip-fg` | `content` | `color` | `default` | `--xh-_tooltip-on` | tooltip 的 content 部件 color 覆盖槽。 |
| `--xh-tooltip-font-size` | `content` | `font-size` | `default` | `--xh-_tooltip-font-size` | tooltip 的 content 部件 font-size 覆盖槽。 |
| `--xh-tooltip-highlight` | `content` | `box-shadow` | `default` | `transparent` | tooltip 的 content 部件 box-shadow 覆盖槽。 |
| `--xh-tooltip-layer` | `positioner` | `z-index` | `default` | `--xh-_layer` | tooltip 的 positioner 部件 z-index 覆盖槽。 |
| `--xh-tooltip-max-w` | `content` | `max-inline-size` | `default` | `--xh-overlay-max-w` | tooltip 的 content 部件 max-inline-size 覆盖槽。 |
| `--xh-tooltip-px` | `content` | `padding-inline` | `default` | `--xh-_tooltip-px` | tooltip 的 content 部件 padding-inline 覆盖槽。 |
| `--xh-tooltip-py` | `content` | `padding-block` | `default` | `--xh-_tooltip-py` | tooltip 的 content 部件 padding-block 覆盖槽。 |
| `--xh-tooltip-radius` | `content` | `border-radius` | `default` | `--xh-shape-control` | tooltip 的 content 部件 border-radius 覆盖槽。 |
| `--xh-tooltip-shadow` | `content` | `box-shadow` | `default` | `--xh-material-frosted-compact-shadow` | tooltip 的 content 部件 box-shadow 覆盖槽。 |
| `--xh-tooltip-trigger-gap` | `trigger` | `gap` | `default` | `--xh-control-gap-sm` | tooltip 的 trigger 部件 gap 覆盖槽。 |
<!-- xh-component-tokens:end -->

### 动效

动效角色：出现（锚定列表）（见[动效规范](../design/motion#角色)）。

共享关键帧 `xh-overlay-slide-in` · `xh-overlay-slide-out` 由 `family/motion.css` 提供，皮肤 `@import` 它，单独引入仍成立。时长与缓动读[动效令牌](../guide/motion)，改令牌即改全局节奏。

皮肤之外还有一段：退场由适配器的退场闸门把关，动画播完才真收起。

系统开启减弱动效时由令牌层统一收敛，皮肤不另作判断。

### RTL

皮肤用逻辑属性排布（`inline-start` 一族），`dir="rtl"` 下自动镜像。
