Files
simple-react-app-kit/packages/lexical/src/root.tsx
T

110 lines
3.0 KiB
TypeScript
Raw Normal View History

"use client"
import { useMemo, useState } from "react"
import type { ComponentProps, ComponentType, ReactNode } from "react"
import { LexicalComposer } from "@lexical/react/LexicalComposer"
import { TextNode } from "lexical"
import { I18nProvider, useI18nProvider } from "@workspace/i18n"
import { cn } from "@workspace/ui/lib/utils"
import { LexicalTextNode } from "./nodes/text-node"
import { lexicalTheme } from "./theme"
import { LexicalEmbedProvider } from "./embed"
import { LexicalHtmlPlugin } from "./plugins/html-value-plugin"
import {
compileLexicalActions,
findLexicalActions,
} from "./action-declarations"
import { LexicalActionsProvider } from "./actions-context"
import { messages as englishMessages } from "./locales/en"
const pluginKeys = new WeakMap<ComponentType, string>()
let nextPluginKey = 0
function getPluginKey(plugin: ComponentType) {
const existingKey = pluginKeys.get(plugin)
if (existingKey) return existingKey
const key = `${plugin.displayName || plugin.name || "plugin"}:${nextPluginKey}`
nextPluginKey += 1
pluginKeys.set(plugin, key)
return key
}
export interface LexicalRootProps extends Omit<
ComponentProps<"div">,
"onChange"
> {
namespace?: string
onChange: (html: string) => void
value?: string
}
function LexicalI18nBoundary({ children }: { children: ReactNode }) {
const hasI18nProvider = useI18nProvider()
if (hasI18nProvider) return children
return (
<I18nProvider catalogs={{ en: englishMessages }} locale="en">
{children}
</I18nProvider>
)
}
export function LexicalRoot({
className,
children,
namespace = "lexical-editor",
onChange,
value,
...props
}: LexicalRootProps) {
const [compiledActions] = useState(() =>
compileLexicalActions(findLexicalActions(children))
)
const initialConfig = useMemo(
() => ({
namespace,
nodes: [
LexicalTextNode,
{
replace: TextNode,
with: (node: TextNode) => new LexicalTextNode(node.getTextContent()),
withKlass: LexicalTextNode,
},
...compiledActions.nodes,
],
onError: (error: Error) => {
throw error
},
theme: lexicalTheme,
}),
[compiledActions.nodes, namespace]
)
return (
<LexicalI18nBoundary>
<LexicalComposer initialConfig={initialConfig}>
<LexicalActionsProvider actions={compiledActions.actions}>
<LexicalEmbedProvider definitions={compiledActions.embeds}>
<div
className={cn(
"overflow-hidden rounded-lg border bg-background",
className
)}
{...props}
>
{children}
</div>
{compiledActions.plugins.map((Plugin) => (
<Plugin key={getPluginKey(Plugin)} />
))}
<LexicalHtmlPlugin value={value} onChange={onChange} />
</LexicalEmbedProvider>
</LexicalActionsProvider>
</LexicalComposer>
</LexicalI18nBoundary>
)
}