# ElementSelector

页面元素选择工具库 —— 让用户通过鼠标点选页面上的任意 DOM 元素。

- 提供 hover 高亮（蓝色虚线 outline）
- 点击元素 / 按 `Enter` 完成选择，按 `ESC` 取消选择
- 支持回调式与 Promise 式两种调用方式
- 重复进入选择模式自动取消上一次（互斥）
- 退出后完整清理（监听器、outline）
- 不污染宿主页面行为（capture 阶段 `preventDefault` + `stopPropagation`）

## 目录

- [安装](#安装)
- [快速开始](#快速开始)
- [API](#api)
- [行为说明](#行为说明)
- [注意事项](#注意事项)

## 安装

通过 `<script>` 标签引入：

```html
<script src="./libs/ElementSelector/ElementSelector.js"></script>
```

或在脚本猫用户脚本中通过 `@require`：

```js
// ==UserScript==
// @name         MyScript
// @require      https://cdn.jsdelivr.net/gh/xxx/scriptcats/libs/ElementSelector/ElementSelector.js
// ==/UserScript==
```

加载完成后即可在 `window.ElementSelector` 上访问。

## 快速开始

### Promise 式

```js
// 选择 HTML
const { element, html, canceled } = await ElementSelector.toSelectHTML();
if (!canceled) {
  console.log('选中了:', element);
  console.log('HTML:', html);
}

// 选择文本
const { element, text, canceled } = await ElementSelector.toSelectText();
if (!canceled) {
  console.log('选中了:', element);
  console.log('文本:', text);
}
```

### 回调式

```js
ElementSelector.toSelectHTML(function (element) {
  console.log('选中了:', element.outerHTML);
});

ElementSelector.toSelectText(function (element) {
  console.log('选中了:', element.innerText);
});
```

## API

### `ElementSelector.toSelectHTML(cb?)`

进入选择模式，等待用户点选一个 DOM 元素。

**参数**
- `cb?: (element: Element) => void` —— 可选回调，仅在成功选择时被调用。

**返回值**：`Promise<{ element: Element | null; html: string; canceled: boolean }>`

- 成功：`{ element: <Element>, html: <element.outerHTML>, canceled: false }`
- 取消：`{ element: null, html: '', canceled: true }`

### `ElementSelector.toSelectText(cb?)`

与 `toSelectHTML` 行为一致，成功时返回 `{ element, text: <element.innerText>, canceled: false }`。

### `ElementSelector.__ELEMENT_SELECTOR_VERSION__`

字符串版本号（当前 `"1.2.0"`）。

## 行为说明

| 用户操作        | 结果                                                                |
| --------------- | ------------------------------------------------------------------- |
| 进入选择模式    | 鼠标指针变为自定义红色靶心 SVG（32×32，热点居中）                  |
| 鼠标 hover 元素 | 该元素获得红色实线 outline + 白色描边 + 红色辉光（详见下方"视觉规格"）|
| 点击元素        | resolve 成功值；不触发宿主页面的 click（如 `<a>` 不会跳转）        |
| 按 `Enter`      | resolve 当前 hover 元素                                              |
| 按 `ESC`        | resolve 取消值（唯一取消方式）                                      |
| 再次调用 API    | 旧 Promise resolve 为取消值，新调用生效                             |

### 视觉规格

为了让目标元素在任何页面上都**一眼可见**，选择模式使用如下视觉规格：

- **鼠标指针**：32×32 SVG 靶心，外环 + 四向瞄准线 + 中心红点，全部带白色描边，红色 `#ff3b30`；SVG 加载失败时 fallback 到浏览器原生 `crosshair`。
- **outline**：`3px solid #ff3b30` + `outline-offset: 2px`（offset 把框线拉到元素外，不遮挡内容）。
- **box-shadow**（三层叠加）：
  - `0 0 0 2px rgba(255, 255, 255, 0.95)` —— 紧贴 outline 的白色描边，让红线在白底页面也清晰；
  - `0 0 0 5px rgba(255, 59, 48, 0.55)` —— 再外一层半透明红环，强化轮廓；
  - `0 8px 24px rgba(255, 59, 48, 0.45)` —— 24px 红色辉光，提供环境感。
- **堆叠上下文**：当 hover 元素原本是 `position: static` 时，自动改为 `relative` 并赋 `z-index: 2147483646`，让 outline / box-shadow 渲染在兄弟元素之上；若元素已有 `position: relative/absolute/fixed` 则**不**强制改写 `position`，仅设置 z-index。

> 设计说明：早期版本曾在浮动面板上提供"确认 / 取消"按钮，但在实际操作中，浮动面板会遮挡 hover 元素、且按钮经常点不到。改用 `ESC` 单一取消方式后体验更直接：默认行为（点击 / Enter）就是确认。

## 注意事项

- **互斥**：同时只能进行一个选择任务。重复调用会自动取消前一次。
- **样式隔离**：仅使用 `outline` + `box-shadow` 实现高亮，不会污染宿主页面的布局。
- **outline 还原**：退出选择模式时，hover 元素的 `outline` / `outline-offset` 会还原到进入前的 inline 值。若元素原本没有 inline outline，会被清空（不影响 CSS 中的 outline 规则）。
- **跨域 frame**：默认不会跨过 `<iframe>` 边界（监听挂在顶层 document 上）。
- **SVG / MathML**：`outerHTML` 在某些浏览器版本中可能抛错，库内已 try/catch，失败时按取消处理。
- **依赖**：纯原生 JavaScript，零依赖。可在 Tampermonkey / 脚本猫 / 普通网页中直接使用。

## 版本

1.2.0