# 🎉 ahmiao React18 Dialog

高性能React18 Dialog组件，支持完整的性能控制和诊断功能。

## 🚀 安装

```bash
npm install @ahmiao666/ahmiao-react-dialog-element
```

## 📖 快速开始

```tsx
# amDialogCustom 弹窗组件使用说明  仅支持React18+ 因为底层订阅使用了 useSyncExternalStore 

## 基本使用

### 使用之前需要先把提供者组件 DialogCustomProvider 挂在到 app 里

```tsx
// 在应用根组件中挂载一次
import { DialogCustomProvider } from '@ahmiao666/ahmiao-react-dialog-element'
import '@ahmiao666/ahmiao-react-dialog-element/index.css'

function AppRoot() {
  return (
    <>
      {/* 你的 App */}
      <DialogCustomProvider />
    </>
  )
}
```

```tsx
import { DialogCustomStaticMethods } from '@ahmiao666/ahmiao-react-dialog-element'

// 方式1: await 方式
const result = await DialogCustomStaticMethods.open(
  ({ onClose }) => (
    <div>
      <p>确定要删除吗？</p>
      <button onClick={() => onClose(true)}>确定</button>
      <button onClick={() => onClose(false)}>取消</button>
    </div>
  ),
  {
    title: "确认删除",
    width: 400
  }
)

if (result) {
  console.log('用户确认删除')
}

// 方式2: .then() 方式
DialogCustomStaticMethods.open(
  ({ onClose }) => (
    <div>
      <p>确定要删除吗？</p>
      <button onClick={() => onClose(true)}>确定</button>
      <button onClick={() => onClose(false)}>取消</button>
    </div>
  ),
  {
    title: "确认删除", 
    width: 400
  }
).then(result => {
  if (result) {
    console.log('用户确认删除')
  }
}).catch(() => {
  console.log('弹窗被取消')
})
```

## 配置选项

```tsx
DialogCustomStaticMethods.open(Component, {
  title: '弹窗标题',            // 标题文本
  width: 500,                 // 宽度：number=vw，string=任意CSS宽度
  showIcon: true,             // 是否显示标题图标
  showClose: true,            // 是否显示关闭按钮
  icon: '📣',                  // 自定义标题图标（可选）
  maskClosable: true,         // 点击遮罩是否可关闭
  maskBg: 'rgba(0,0,0,0.6)',  // 遮罩背景色（仅颜色）
  familyName: 'settings',     // 弹窗命名空间（用于多弹窗并存）
  onNext: (v) => {},          // 可选的下一步回调（会传给内容组件）
  props: { data: '数据' }      // 传递给内容组件的自定义 props
})
```

### 遮罩与样式

- 仅支持通过 `maskBg` 修改遮罩颜色，默认值：`rgba(0, 0, 0, 0.4)`。
- 当同时打开多个弹窗时，遮罩只渲染一次，颜色取“最上层弹窗”的 `maskBg`。

```ts
DialogCustomStaticMethods.open(Comp, {
  maskBg: 'rgba(0,0,0,0.6)'
})
```

## 多弹窗
```tsx
// 打开多个弹窗
DialogCustomStaticMethods.openWithFamily("settings", SettingsComponent)
DialogCustomStaticMethods.openWithFamily("profile", ProfileComponent)

// 或者通过 options.familyName 指定
DialogCustomStaticMethods.open(SettingsComponent, { familyName: 'settings' })
DialogCustomStaticMethods.open(ProfileComponent, { familyName: 'profile' })

// 关闭指定弹窗
DialogCustomStaticMethods.close("settings")

// 关闭所有弹窗
DialogCustomStaticMethods.closeAll()
```

## 回调方式

### 方式1: onClose回调
```tsx
const result = await DialogCustomStaticMethods.open(
  ({ onClose }) => (
    <div>
      <button onClick={() => onClose("保存")}>保存</button>
      <button onClick={() => onClose(null)}>取消</button>
    </div>
  )
)
// result = "保存" 或 null
```

### 方式2: props回调
```tsx
const handleSave = (data) => console.log("保存:", data)

await DialogCustomStaticMethods.open(
  ({ onSave, onClose }) => (
    <div>
      <button onClick={() => onSave("数据")}>保存</button>
      <button onClick={() => onClose()}>关闭</button>
    </div>
  ),
  {
    props: { onSave: handleSave }
  }
)
```

## 状态读取与订阅

> 这些 Hook/方法用于“读取弹窗状态”。推荐优先使用只读选择器，避免直接接触底层 Map。

### useHasOpenDialog

是否存在任意弹窗打开（订阅式）。

```tsx
import { useHasOpenDialog } from '@/am-ui/am-dialog'

export function Header() {
  const hasOpen = useHasOpenDialog()
  return hasOpen ? null : <TopBar />
}
```

### useFamilyVisible

指定 family 是否打开（订阅式）。

```tsx
import { useFamilyVisible } from '@/am-ui/am-dialog'

const profileOpen = useFamilyVisible('profile')
```

### useDialogSelector

通用选择器 Hook：订阅式返回派生值，避免暴露底层 Map。

```tsx
import { useDialogSelector } from '@/am-ui/am-dialog'

// 打开中的 family 列表
const openFamilies = useDialogSelector((states) =>
  Array.from(states.entries())
    .filter(([, s]) => s.visible)
    .map(([family]) => family),
)

// 顶层弹窗标题（示例）
const topTitle = useDialogSelector((states) => {
  const visible = Array.from(states.values()).filter((s) => s.visible && s.options)
  const top = visible[visible.length - 1]
  return top?.options?.title ?? ''
})
```

### getDialogFrozenSnapshot

一次性只读快照（非订阅）。适合在非 React 或初始化时读取。

```ts
import { getDialogFrozenSnapshot } from '@/am-ui/am-dialog'

const snap = getDialogFrozenSnapshot()
for (const [family, state] of snap) {
  console.log(family, state.visible, state.options?.title)
}
```

### subscribeHasOpen

非 React 环境订阅“是否有弹窗打开”的变化（会立即回调一次当前值）。

```ts
import { subscribeHasOpen } from '@/am-ui/am-dialog'

const unsubscribe = subscribeHasOpen((hasOpen) => {
  console.log('hasOpen changed:', hasOpen)
})

// 需要时机合适地取消订阅
unsubscribe()
```

> 写操作请始终使用现有 API：`DialogCustomStaticMethods.open/openWithFamily/close/closeAll` 或内容组件内的 `onClose`/`onNext`，不要直接修改底层 Map。

```

## 🔗 相关链接

- [完整文档](https://github.com/ahmiao666/ahmiao-react-dialog-element)
- [GitHub仓库](https://github.com/ahmiao666/ahmiao-react-dialog-element)
