# RichText

基于 [lexical](https://lexical.dev/) 的轻量富文本编辑器，支持块类型、缩进、对齐、字号、图片等格式化能力，并提供只读展示组件 RichTextView。

## 适用场景

- 表单中需要带格式文本编辑（加粗、对齐、标题、列表等）的场景
- 需要支持图片插入的内容编辑
- 富文本 HTML 内容的只读展示（使用 RichTextView）
- 需要通过命令式 API 获取或设置编辑器内容

## Props

### RichText Props

| 名称 | 类型 | 默认值 | 必填 | 说明 |
|------|------|--------|------|------|
| className | `string` | `""` | 否 | 自定义 class |
| disabled | `boolean` | `-` | 否 | 是否禁用 |
| defaultValue | `string` | `-` | 否 | 默认值，作为初始值的 html string |
| withBlockType | `boolean` | `true` | 否 | 是否使用"块类型" |
| withIndent | `boolean` | `true` | 否 | 是否使用"缩进" |
| withAlignment | `boolean` | `true` | 否 | 是否使用"对齐" |
| withFontSize | `boolean` | `true` | 否 | 是否使用"文字尺寸" |
| withImage | `boolean` | `false` | 否 | 是否使用"图片" |
| imageMaxSize | `number` | `100 * 1024` | 否 | 插入图片的最大文件大小（bytes） |
| imageInsertWidthMax | `number` | `480` | 否 | 图片插入时最大尺寸，0 为不做限制 |
| onImageAccept | `(file: File) => Promise<string>` | `-` | 否 | 选择图片后的回调，async 返回 image src |
| enableImageSrc | `boolean` | `false` | 否 | 是否可输入指定 image src |
| onChange | `(html: string) => void` | `-` | 否 | 值变化回调 |
| onFocus | `FocusEventHandler<HTMLDivElement>` | `-` | 否 | 聚焦回调 |
| onBlur | `FocusEventHandler<HTMLDivElement>` | `-` | 否 | 失焦回调 |
| placeholder | `string` | `-` | 否 | 占位提示语 |
| maxHeight | `number` | `-` | 否 | 输入区域最大高度，需 >= minHeight，否则无效 |
| minHeight | `number` | `160` | 否 | 输入区最小高度 |

### RichTextHandle Props（Ref 命令式 API）

| 名称 | 类型 | 必填 | 说明 |
|------|------|------|------|
| getValue | `() => string` | 是 | 获取当前值（html string） |
| setValue | `(value: string) => void` | 是 | 设置编辑器内容 |
| focus | `(callbackFn?: () => void, options?: EditorFocusOptions) => void` | 是 | 聚焦编辑器 |
| blur | `() => void` | 是 | 失焦编辑器 |

### RichTextView Props

| 名称 | 类型 | 默认值 | 必填 | 说明 |
|------|------|--------|------|------|
| className | `string` | `""` | 否 | 自定义 class |
| value | `string` | `-` | 否 | 内容值，html string |

## 典型用法

### 基础用法（命令式读写）

> RichText 是非受控（uncontrolled）的。通过 `defaultValue` 设置初始值，通过 ref 的 `getValue()` / `setValue()` 读写内容；`onChange` 也可用于监听变化。

```tsx
import { useRef, ElementRef } from 'react'
import { RichText, Button } from '@befe/brick'

function Demo() {
    const refRichText = useRef<ElementRef<typeof RichText>>(null)

    const submit = () => {
        const htmlString = refRichText.current?.getValue()
        console.log({ htmlString })
    }

    const setValue = () => {
        refRichText.current?.setValue('<p>hello!</p>')
    }

    return (
        <div>
            <Button onClick={submit}>Submit</Button>
            <Button onClick={setValue}>Set Value</Button>
            <RichText
                ref={refRichText}
                defaultValue="<p>hello!</p>"
                onChange={(value) => console.log('changed', value)}
                withImage
            />
        </div>
    )
}
```

### 禁用状态

```tsx
import { RichText, Switch } from '@befe/brick'
import { useState } from 'react'

function Demo() {
    const [editable, setEditable] = useState(false)
    return (
        <div>
            <Switch
                checked={editable}
                onChange={setEditable}
                checkedLabel="启用"
                uncheckedLabel="禁用"
            />
            <RichText disabled={!editable} />
        </div>
    )
}
```

### 只读展示（RichTextView）

```tsx
import { RichTextView } from '@befe/brick'

const value = `
<p class="brick-rich-text-paragraph" dir="ltr">
    <span style="white-space: pre-wrap;">I'm </span>
    <span style="white-space: pre-wrap; color: #F24B60;">rich</span>
</p>
`

function Demo() {
    return <RichTextView value={value} />
}
```

### 扩展 Lexical 原生插件

```tsx
import { RichText } from '@befe/brick'
import { OnChangePlugin } from '@lexical/react/LexicalOnChangePlugin'
import { AutoFocusPlugin } from '@lexical/react/LexicalAutoFocusPlugin'
import { $generateHtmlFromNodes } from '@lexical/html'

function Demo() {
    const handleChange = (editorState, editor) => {
        console.log({
            html: editorState.read(() => $generateHtmlFromNodes(editor)),
            json: editorState.toJSON(),
        })
    }

    return (
        <RichText>
            <OnChangePlugin onChange={handleChange} />
            <AutoFocusPlugin />
        </RichText>
    )
}
```

## 注意事项

- RichText 是**非受控**组件，不支持受控的 `value` prop；只能用 `defaultValue` 设置初始值，运行时修改须通过 ref 的 `setValue()`
- `onChange` 在组件挂载初始化时也会调用一次
- 需要只读展示富文本内容时，使用 `RichTextView` 而非 `RichText`
- `maxHeight` 需要 >= `minHeight`，否则无效
- `withImage` 为 `false` 时，`imageMaxSize`、`imageInsertWidthMax`、`onImageAccept`、`enableImageSrc` 均无效
- 支持通过 `children` 传入 Lexical 插件进行功能扩展
