# Popper

基础弹层定位组件，基于 Popper.js v2 实现。作为底层组件为 Popover、Tooltip、MenuPopper 等提供定位能力。

## 适用场景

- 需要自定义弹层行为的场景
- 构建基于浮层定位的复合组件
- 需要精细控制弹层触发方式、位置、箭头的场景

## Props

### PopperTrigger Props

| 名称 | 类型 | 默认值 | 必填 | 说明 |
|------|------|--------|------|------|
| type | `"click" \| "hover" \| "focus"` | `"click"` | 否 | 触发方式 |
| visible | `boolean` | - | 否 | popper 是否显示控制值 |
| target | `(() => HTMLElement \| null \| void) \| HTMLElement \| null` | - | 否 | 指定 popper 的目标元素（不接受 ReactNode） |
| defaultVisible | `boolean` | `false` | 否 | popper 是否显示默认值 |
| onChange | `(visible: boolean, e?: Event \| SyntheticEvent<Element, Event>) => void` | - | 否 | visible 变化回调 |
| beforeChange | `(visible: boolean) => boolean \| void \| Promise<unknown>` | - | 否 | visible 变化前回调，返回 false 或 reject 可阻止变化 |
| mouseEnterDelay | `number` | `150` | 否 | mouseEnter 延迟触发（毫秒） |
| mouseLeaveDelay | `number` | `60` | 否 | mouseLeave 延迟触发（毫秒） |
| focusDelay | `number` | `150` | 否 | focus 延迟触发（毫秒） |
| blurDelay | `number` | `0` | 否 | blur 延迟触发（毫秒） |
| shouldHideParent | `boolean` | - | 否 | 是否隐藏时隐藏 parent |
| shouldHideOnMousedownDocument | `boolean \| ((e: Event) => boolean)` | `true` | 否 | 点击外部时是否隐藏 popper，设为 false 可禁用 |

### Popper Props

| 名称 | 类型 | 默认值 | 必填 | 说明 |
|------|------|--------|------|------|
| className | `string` | `""` | 否 | 自定义 class |
| visible | `boolean` | - | 否 | 是否显示 |
| target | `Element \| VirtualElement \| (() => Element \| VirtualElement \| void \| null)` | - | 否 | 目标元素 |
| placement | `Placement` | `"bottom"` | 否 | 浮层相对于 target 的位置 |
| strategy | `PositioningStrategy` | `"absolute"` | 否 | 位置策略；target 在 fixed 容器中时设为 `"fixed"` |
| modifiers | `Partial<Modifier<any, any>>[]` | - | 否 | Popper.js v2 modifier 设置 |
| refPopperWrap | `Ref<HTMLDivElement>` | - | 否 | popperWrap 的 element ref |
| matchMinWidthToTarget | `boolean` | `false` | 否 | popperWrap 最小宽度匹配 target 宽度 |
| matchWidthToTarget | `boolean` | - | 否 | popperWrap 宽度匹配 target 宽度 |
| onMouseEnter | `MouseEventHandler<HTMLDivElement>` | - | 否 | 鼠标进入 popper wrap 回调 |
| onMouseLeave | `MouseEventHandler<HTMLDivElement>` | - | 否 | 鼠标离开 popper wrap 回调 |
| onChange | `(popperState: PopperState) => void` | - | 否 | popper 总状态更新回调（预留） |
| withArrow | `boolean` | `false` | 否 | 是否带箭头 |
| arrowOffset | `number \| "auto"` | `"auto"` | 否 | 箭头偏移量，仅在 placement 为 *-end/*-start 时有效 |
| destroyOnHide | `boolean` | `true` | 否 | 是否在隐藏时销毁 |
| disablePortal | `boolean` | - | 否 | 禁用 portal，维持 DOM 层级 |
| portalContainer | `HTMLElement \| (() => HTMLElement)` | - | 否 | 指定 portal 挂载容器，默认 document.body |

## 典型用法

### 基础用法（PopperTrigger + Popper）

```tsx
import { PopperTrigger, Popper, Button, Input } from '@befe/brick'

// click 触发
<PopperTrigger>
    <Button type="important">click me</Button>
    <Popper className="demo-popper"><div>hello</div></Popper>
</PopperTrigger>

// hover 触发
<PopperTrigger type="hover">
    <Button type="intensive">hover me</Button>
    <Popper placement="top"><div>提示内容</div></Popper>
</PopperTrigger>

// focus 触发（输入框场景）
<PopperTrigger type="focus">
    <Input placeholder="focus me" />
    <Popper placement="bottom-start"><div>输入提示</div></Popper>
</PopperTrigger>
```

### 位置与箭头

```tsx
// placement 支持：top/bottom/left/right 及各自的 -start/-end 变体
<PopperTrigger type="hover">
    <Button>bottom-start</Button>
    <Popper placement="bottom-start" withArrow>
        <div>这是一小段话术文字描述</div>
    </Popper>
</PopperTrigger>
```

### 受控 visible（直接控制 Popper）

```tsx
import { useRef, useState } from 'react'
import { Button, Popper } from '@befe/brick'

const [visible, setVisible] = useState(false)
const refTarget = useRef<HTMLButtonElement>(null)

<Button refHTMLElement={refTarget} onClick={() => setVisible(!visible)}>
    Controlled Visible
</Button>
<Popper target={() => refTarget.current} visible={visible}>
    <div>hello</div>
</Popper>
```

### beforeChange 拦截

```tsx
<PopperTrigger beforeChange={(visible) => {
    if (visible && !confirm('show?')) return false
    return true
}}>
    <Button>before change</Button>
    <Popper><div>content</div></Popper>
</PopperTrigger>
```

## 注意事项

- 对于不触发 mouseEnter/mouseLeave 的 disabled 元素，需要包一层非 disabled 的元素并用 `::after` 覆盖以接收事件
- `props.positionFixed` 已废弃（从 brick@0.2.44 起），请使用 `props.strategy`
- `props.modifiers` 类型已变更为 Popper.js v2 格式 `Array<Partial<Modifier<any, any>>>`
- `arrowOffset` 为 `'auto'` 时，popper 尺寸小于 target 时会自动对齐边缘，箭头相对 popper 居中
- PopperTrigger 暂不支持 `props.disabled`，可通过 `beforeChange`/`onChange` 实现条件触发
