From 682e58255a36101ec8917940eb87b1137de1c72c Mon Sep 17 00:00:00 2001 From: culpen90 Date: Sat, 13 Jun 2026 09:49:52 -0400 Subject: [PATCH 1/2] Add React MathType example --- examples/react-mathtype/README.md | 27 ++ examples/react-mathtype/index.html | 13 + examples/react-mathtype/package.json | 33 +++ examples/react-mathtype/src/App.tsx | 75 ++++++ .../react-mathtype/src/MathTypeContext.tsx | 78 ++++++ examples/react-mathtype/src/MathTypeData.ts | 117 ++++++++ .../react-mathtype/src/MathTypeGlobals.ts | 89 +++++++ examples/react-mathtype/src/MathTypeNode.tsx | 249 ++++++++++++++++++ .../react-mathtype/src/MathTypePlugin.tsx | 166 ++++++++++++ examples/react-mathtype/src/main.tsx | 22 ++ examples/react-mathtype/src/styles.css | 120 +++++++++ examples/react-mathtype/src/vite-env.d.ts | 3 + examples/react-mathtype/tsconfig.json | 21 ++ examples/react-mathtype/tsconfig.node.json | 11 + .../react-mathtype/vite.config.monorepo.ts | 15 ++ examples/react-mathtype/vite.config.ts | 13 + 16 files changed, 1052 insertions(+) create mode 100644 examples/react-mathtype/README.md create mode 100644 examples/react-mathtype/index.html create mode 100644 examples/react-mathtype/package.json create mode 100644 examples/react-mathtype/src/App.tsx create mode 100644 examples/react-mathtype/src/MathTypeContext.tsx create mode 100644 examples/react-mathtype/src/MathTypeData.ts create mode 100644 examples/react-mathtype/src/MathTypeGlobals.ts create mode 100644 examples/react-mathtype/src/MathTypeNode.tsx create mode 100644 examples/react-mathtype/src/MathTypePlugin.tsx create mode 100644 examples/react-mathtype/src/main.tsx create mode 100644 examples/react-mathtype/src/styles.css create mode 100644 examples/react-mathtype/src/vite-env.d.ts create mode 100644 examples/react-mathtype/tsconfig.json create mode 100644 examples/react-mathtype/tsconfig.node.json create mode 100644 examples/react-mathtype/vite.config.monorepo.ts create mode 100644 examples/react-mathtype/vite.config.ts diff --git a/examples/react-mathtype/README.md b/examples/react-mathtype/README.md new file mode 100644 index 00000000000..dc7de0d875c --- /dev/null +++ b/examples/react-mathtype/README.md @@ -0,0 +1,27 @@ +# React MathType example + +This example demonstrates a standalone WIRIS MathType integration for a +Lexical React editor. It keeps MathType as an example dependency and stores +formulas in a custom Lexical node instead of allowing MathType to mutate the +Lexical contenteditable directly. + +The bridge uses `@wiris/mathtype-generic` to open the MathType/ChemType UI, +converts the generated MathML image into `MathTypeNode` state inside +`editor.update`, and reopens MathType when a formula node is double-clicked. + +MathType stores formulas as MathML. The rendered formula image is kept in node +state for the demo, while DOM export emits the same `img.Wirisformula` shape +that MathType's parser expects. + +## Running + +```bash +pnpm install +pnpm run dev +pnpm run build +pnpm run typecheck +``` + +MathType may require a WIRIS license or self-hosted services for production +use. See the MathType generic integration documentation for service +configuration details. diff --git a/examples/react-mathtype/index.html b/examples/react-mathtype/index.html new file mode 100644 index 00000000000..661a8d6db47 --- /dev/null +++ b/examples/react-mathtype/index.html @@ -0,0 +1,13 @@ + + + + + + + Lexical MathType Example + + +
+ + + diff --git a/examples/react-mathtype/package.json b/examples/react-mathtype/package.json new file mode 100644 index 00000000000..423a238ee54 --- /dev/null +++ b/examples/react-mathtype/package.json @@ -0,0 +1,33 @@ +{ + "name": "@lexical/react-mathtype-example", + "private": true, + "version": "0.45.0", + "type": "module", + "scripts": { + "dev": "vite", + "monorepo:dev": "vite -c vite.config.monorepo.ts", + "build": "tsc && vite build", + "preview": "vite preview", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@lexical/react": "0.45.0", + "@wiris/mathtype-generic": "^8.15.2", + "lexical": "0.45.0", + "react": "^19.2.5", + "react-dom": "^19.2.5" + }, + "devDependencies": { + "@types/react": "^19.2.14", + "@types/react-dom": "^19.1.9", + "@vitejs/plugin-react": "^5.0.2", + "cross-env": "^7.0.3", + "typescript": "^5.9.2", + "vite": "^7.3.2" + }, + "optionalDependencies": { + "@rollup/rollup-linux-x64-gnu": "4.52.0", + "@rollup/rollup-darwin-arm64": "4.52.0", + "@rollup/rollup-win32-x64-msvc": "4.52.0" + } +} diff --git a/examples/react-mathtype/src/App.tsx b/examples/react-mathtype/src/App.tsx new file mode 100644 index 00000000000..040524f36a3 --- /dev/null +++ b/examples/react-mathtype/src/App.tsx @@ -0,0 +1,75 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import {AutoFocusPlugin} from '@lexical/react/LexicalAutoFocusPlugin'; +import {LexicalComposer} from '@lexical/react/LexicalComposer'; +import {ContentEditable} from '@lexical/react/LexicalContentEditable'; +import {LexicalErrorBoundary} from '@lexical/react/LexicalErrorBoundary'; +import {HistoryPlugin} from '@lexical/react/LexicalHistoryPlugin'; +import {RichTextPlugin} from '@lexical/react/LexicalRichTextPlugin'; +import { + $createParagraphNode, + $createTextNode, + $getRoot, + ParagraphNode, + TextNode, +} from 'lexical'; + +import {MathTypeProvider} from './MathTypeContext'; +import {MathTypeNode} from './MathTypeNode'; +import {MathTypePlugin} from './MathTypePlugin'; + +const placeholder = 'Write a math note...'; + +const theme = { + paragraph: 'editor-paragraph', +}; + +const editorConfig = { + editorState: () => { + $getRoot().append( + $createParagraphNode().append( + $createTextNode('Use the MathType toolbar to insert a formula. '), + ), + ); + }, + namespace: 'MathType Example', + nodes: [ParagraphNode, TextNode, MathTypeNode], + onError(error: Error) { + throw error; + }, + theme, +}; + +export default function App() { + return ( + + +
+ +
+ {placeholder}
+ } + /> + } + ErrorBoundary={LexicalErrorBoundary} + /> + + +
+ +
+
+ ); +} diff --git a/examples/react-mathtype/src/MathTypeContext.tsx b/examples/react-mathtype/src/MathTypeContext.tsx new file mode 100644 index 00000000000..c6032d05252 --- /dev/null +++ b/examples/react-mathtype/src/MathTypeContext.tsx @@ -0,0 +1,78 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import type {MathTypeFormula} from './MathTypeData'; +import type {MathTypeIntegrationInstance} from './MathTypeGlobals'; +import type {NodeKey} from 'lexical'; +import type {JSX, MutableRefObject, PropsWithChildren} from 'react'; + +import {createContext, useCallback, useContext, useMemo, useRef} from 'react'; + +import {createImageFromFormula} from './MathTypeData'; + +type MathTypeContextValue = { + editFormula: (nodeKey: NodeKey, formula: MathTypeFormula) => boolean; + integrationRef: MutableRefObject; + pendingNodeKeyRef: MutableRefObject; +}; + +const MathTypeContext = createContext(null); + +export function MathTypeProvider({children}: PropsWithChildren): JSX.Element { + const integrationRef = useRef(null); + const pendingNodeKeyRef = useRef(null); + + const editFormula = useCallback( + (nodeKey: NodeKey, formula: MathTypeFormula): boolean => { + const integration = integrationRef.current; + if (integration === null) { + return false; + } + pendingNodeKeyRef.current = nodeKey; + const temporalImage = createImageFromFormula(formula); + integration.core.editionProperties.temporalImage = temporalImage; + integration.core.editionProperties.dbclick = true; + integration.core.editionProperties.isNewElement = false; + + const customEditors = integration.core.getCustomEditors(); + customEditors.disable(); + if (formula.customEditor !== null) { + customEditors.enable(formula.customEditor); + } + + integration.openExistingFormulaEditor(); + return true; + }, + [], + ); + + const value = useMemo( + () => ({ + editFormula, + integrationRef, + pendingNodeKeyRef, + }), + [editFormula], + ); + + return ( + + {children} + + ); +} + +export function useMathTypeContext(): MathTypeContextValue { + const context = useContext(MathTypeContext); + if (context === null) { + throw new Error( + 'MathType components must be rendered inside MathTypeProvider.', + ); + } + return context; +} diff --git a/examples/react-mathtype/src/MathTypeData.ts b/examples/react-mathtype/src/MathTypeData.ts new file mode 100644 index 00000000000..3d09fbd64d0 --- /dev/null +++ b/examples/react-mathtype/src/MathTypeData.ts @@ -0,0 +1,117 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +export const WIRIS_FORMULA_CLASS = 'Wirisformula'; +export const WIRIS_MATHML_ATTRIBUTE = 'data-mathml'; +export const WIRIS_CUSTOM_EDITOR_ATTRIBUTE = 'data-custom-editor'; + +export type MathTypeFormula = { + altText: string; + customEditor: null | string; + height: null | number; + mathML: string; + src: string; + width: null | number; +}; + +export function parseOptionalNumber(value: string | null): null | number { + if (value === null || value === '') { + return null; + } + const parsed = Number(value); + return Number.isFinite(parsed) && parsed > 0 ? parsed : null; +} + +export function encodeMathML(mathML: string): string { + const wirisPlugin = window.WirisPlugin; + const wirisMathML = + wirisPlugin === undefined ? undefined : wirisPlugin.MathML; + if (wirisMathML) { + return wirisMathML.safeXmlEncode(mathML); + } + return mathML + .split('&') + .join('\u00a7') + .split('<') + .join('\u00ab') + .split('>') + .join('\u00bb') + .split('"') + .join('\u00a8') + .split("'") + .join('`'); +} + +export function decodeMathML(encodedMathML: string): string { + const wirisPlugin = window.WirisPlugin; + const wirisMathML = + wirisPlugin === undefined ? undefined : wirisPlugin.MathML; + if (wirisMathML) { + return wirisMathML.safeXmlDecode(encodedMathML); + } + return encodedMathML + .split('«') + .join('<') + .split('»') + .join('>') + .split('¨') + .join('"') + .split('"') + .join('"') + .split('\u00ab') + .join('<') + .split('\u00bb') + .join('>') + .split('\u00a8') + .join('"') + .split('\u00a7') + .join('&') + .split('`') + .join("'"); +} + +export function createFormulaFromImage( + image: HTMLImageElement, + fallbackMathML?: string, +): MathTypeFormula { + const encodedMathML = image.getAttribute(WIRIS_MATHML_ATTRIBUTE); + const mathML = + fallbackMathML ?? + (encodedMathML === null ? '' : decodeMathML(encodedMathML)); + return { + altText: image.getAttribute('alt') ?? '', + customEditor: image.getAttribute(WIRIS_CUSTOM_EDITOR_ATTRIBUTE), + height: parseOptionalNumber(image.getAttribute('height')), + mathML, + src: image.getAttribute('src') ?? '', + width: parseOptionalNumber(image.getAttribute('width')), + }; +} + +export function createImageFromFormula( + formula: MathTypeFormula, +): HTMLImageElement { + const image = document.createElement('img'); + image.align = 'middle'; + image.className = WIRIS_FORMULA_CLASS; + image.src = formula.src; + image.alt = formula.altText; + image.setAttribute('role', 'math'); + image.setAttribute(WIRIS_MATHML_ATTRIBUTE, encodeMathML(formula.mathML)); + if (formula.customEditor !== null) { + image.setAttribute(WIRIS_CUSTOM_EDITOR_ATTRIBUTE, formula.customEditor); + } + if (formula.width !== null) { + image.setAttribute('width', String(formula.width)); + } + if (formula.height !== null) { + image.setAttribute('height', String(formula.height)); + } + image.style.maxWidth = 'none'; + return image; +} diff --git a/examples/react-mathtype/src/MathTypeGlobals.ts b/examples/react-mathtype/src/MathTypeGlobals.ts new file mode 100644 index 00000000000..55aa57fb604 --- /dev/null +++ b/examples/react-mathtype/src/MathTypeGlobals.ts @@ -0,0 +1,89 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +export type MathTypeCustomEditors = { + disable: () => void; + enable: (name: string) => void; +}; + +export type MathTypeIntegrationInstance = { + core: { + editionProperties: { + dbclick?: boolean; + isNewElement?: boolean; + temporalImage?: HTMLImageElement | null; + }; + getCustomEditors: () => MathTypeCustomEditors; + }; + destroy: () => void; + init: () => void; + insertFormula: ( + focusElement: HTMLElement | Window, + windowTarget: Window, + mathML: string, + wirisProperties: null | object, + ) => object; + listeners: { + fire: (eventName: string, payload: object) => void; + }; + openExistingFormulaEditor: () => void; + openNewFormulaEditor: () => void; + toolbar: HTMLElement | null; +}; + +export type MathTypeIntegrationProperties = { + integrationParameters?: { + editorParameters?: { + language?: string; + }; + serviceProviderProperties?: { + server?: string; + URI?: string; + }; + }; + target: HTMLElement; + toolbar: HTMLElement; +}; + +export type WirisPluginGlobal = { + Configuration?: { + get: (key: string) => false | string; + }; + currentInstance?: MathTypeIntegrationInstance | null; + GenericIntegration: new ( + properties: MathTypeIntegrationProperties, + ) => MathTypeIntegrationInstance; + MathML: { + safeXmlDecode: (mathML: string) => string; + safeXmlEncode: (mathML: string) => string; + }; + Parser: { + endParse: (html: string) => string; + initParse: (html: string, language?: string) => string; + mathmlToImgObject: ( + document: Document, + mathML: string, + wirisProperties: null | object, + language?: string, + ) => HTMLImageElement; + }; +}; + +declare global { + interface Window { + WirisPlugin?: WirisPluginGlobal; + } +} + +export function getWirisPlugin(): WirisPluginGlobal { + const {WirisPlugin} = window; + if (WirisPlugin === undefined) { + throw new Error('MathType generic integration did not initialize.'); + } + return WirisPlugin; +} diff --git a/examples/react-mathtype/src/MathTypeNode.tsx b/examples/react-mathtype/src/MathTypeNode.tsx new file mode 100644 index 00000000000..7f280fd521f --- /dev/null +++ b/examples/react-mathtype/src/MathTypeNode.tsx @@ -0,0 +1,249 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import type {MathTypeFormula} from './MathTypeData'; +import type { + DOMConversionOutput, + DOMExportOutput, + EditorConfig, + LexicalNode, + NodeKey, + StateValueOrUpdater, +} from 'lexical'; +import type {JSX} from 'react'; + +import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext'; +import {useLexicalEditable} from '@lexical/react/useLexicalEditable'; +import {useLexicalNodeSelection} from '@lexical/react/useLexicalNodeSelection'; +import { + $create, + $getState, + $setState, + buildImportMap, + CLICK_COMMAND, + COMMAND_PRIORITY_LOW, + createState, + DecoratorNode, +} from 'lexical'; +import {useCallback, useEffect} from 'react'; + +import {useMathTypeContext} from './MathTypeContext'; +import { + createFormulaFromImage, + createImageFromFormula, + encodeMathML, + WIRIS_FORMULA_CLASS, + WIRIS_MATHML_ATTRIBUTE, +} from './MathTypeData'; + +const mathMLState = /* @__PURE__ */ createState('mathML', { + parse: value => (typeof value === 'string' ? value : ''), +}); + +const srcState = /* @__PURE__ */ createState('src', { + parse: value => (typeof value === 'string' ? value : ''), +}); + +const altTextState = /* @__PURE__ */ createState('altText', { + parse: value => (typeof value === 'string' ? value : ''), +}); + +const widthState = /* @__PURE__ */ createState('width', { + parse: value => (typeof value === 'number' ? value : null), +}); + +const heightState = /* @__PURE__ */ createState('height', { + parse: value => (typeof value === 'number' ? value : null), +}); + +const customEditorState = /* @__PURE__ */ createState('customEditor', { + parse: value => (typeof value === 'string' ? value : null), +}); + +function $convertMathTypeImage( + domNode: HTMLElement, +): DOMConversionOutput | null { + if (!(domNode instanceof HTMLImageElement)) { + return null; + } + const hasWirisFormula = + domNode.classList.contains(WIRIS_FORMULA_CLASS) || + domNode.hasAttribute(WIRIS_MATHML_ATTRIBUTE); + if (!hasWirisFormula) { + return null; + } + return {node: $createMathTypeNode(createFormulaFromImage(domNode))}; +} + +export class MathTypeNode extends DecoratorNode { + $config() { + return this.config('mathtype', { + extends: DecoratorNode, + importDOM: buildImportMap({ + img: domNode => + domNode instanceof HTMLImageElement && + (domNode.classList.contains(WIRIS_FORMULA_CLASS) || + domNode.hasAttribute(WIRIS_MATHML_ATTRIBUTE)) + ? {conversion: $convertMathTypeImage, priority: 3} + : null, + }), + stateConfigs: [ + {flat: true, stateConfig: mathMLState}, + {flat: true, stateConfig: srcState}, + {flat: true, stateConfig: altTextState}, + {flat: true, stateConfig: widthState}, + {flat: true, stateConfig: heightState}, + {flat: true, stateConfig: customEditorState}, + ], + }); + } + + createDOM(_config: EditorConfig): HTMLElement { + const element = document.createElement('span'); + element.className = 'editor-mathtype'; + return element; + } + + updateDOM(): false { + return false; + } + + getFormula(): MathTypeFormula { + return { + altText: $getState(this, altTextState), + customEditor: $getState(this, customEditorState), + height: $getState(this, heightState), + mathML: $getState(this, mathMLState), + src: $getState(this, srcState), + width: $getState(this, widthState), + }; + } + + setFormula(formula: MathTypeFormula): this { + return $setState(this, mathMLState, formula.mathML) + .setSrc(formula.src) + .setAltText(formula.altText) + .setWidth(formula.width) + .setHeight(formula.height) + .setCustomEditor(formula.customEditor); + } + + setSrc(valueOrUpdater: StateValueOrUpdater): this { + return $setState(this, srcState, valueOrUpdater); + } + + setAltText(valueOrUpdater: StateValueOrUpdater): this { + return $setState(this, altTextState, valueOrUpdater); + } + + setWidth(valueOrUpdater: StateValueOrUpdater): this { + return $setState(this, widthState, valueOrUpdater); + } + + setHeight(valueOrUpdater: StateValueOrUpdater): this { + return $setState(this, heightState, valueOrUpdater); + } + + setCustomEditor( + valueOrUpdater: StateValueOrUpdater, + ): this { + return $setState(this, customEditorState, valueOrUpdater); + } + + exportDOM(): DOMExportOutput { + return {element: createImageFromFormula(this.getFormula())}; + } + + getTextContent(): string { + const formula = this.getFormula(); + return formula.altText || formula.mathML; + } + + decorate(): JSX.Element { + return ( + + ); + } +} + +export function $createMathTypeNode(formula: MathTypeFormula): MathTypeNode { + return $create(MathTypeNode).setFormula(formula); +} + +export function $isMathTypeNode( + node: LexicalNode | null | undefined, +): node is MathTypeNode { + return node instanceof MathTypeNode; +} + +function MathTypeFormulaComponent({ + formula, + nodeKey, +}: { + formula: MathTypeFormula; + nodeKey: NodeKey; +}): JSX.Element { + const [editor] = useLexicalComposerContext(); + const isEditable = useLexicalEditable(); + const [isSelected, setSelected, clearSelection] = + useLexicalNodeSelection(nodeKey); + const {editFormula} = useMathTypeContext(); + + const onClick = useCallback( + (event: MouseEvent) => { + const dom = editor.getElementByKey(nodeKey); + if (dom === null || !dom.contains(event.target as Node)) { + return false; + } + if (event.shiftKey) { + setSelected(!isSelected); + } else { + clearSelection(); + setSelected(true); + } + return true; + }, + [clearSelection, editor, isSelected, nodeKey, setSelected], + ); + + useEffect(() => { + return editor.registerCommand(CLICK_COMMAND, onClick, COMMAND_PRIORITY_LOW); + }, [editor, onClick]); + + useEffect(() => { + const dom = editor.getElementByKey(nodeKey); + if (dom === null) { + return; + } + dom.classList.toggle('selected', isSelected && isEditable); + }, [editor, isEditable, isSelected, nodeKey]); + + return ( + {formula.altText} { + event.preventDefault(); + if (isEditable) { + editFormula(nodeKey, formula); + } + }} + role="math" + src={formula.src} + title={isEditable ? 'Edit formula' : undefined} + width={formula.width ?? undefined} + /> + ); +} diff --git a/examples/react-mathtype/src/MathTypePlugin.tsx b/examples/react-mathtype/src/MathTypePlugin.tsx new file mode 100644 index 00000000000..450f7ba1f68 --- /dev/null +++ b/examples/react-mathtype/src/MathTypePlugin.tsx @@ -0,0 +1,166 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import type {MathTypeFormula} from './MathTypeData'; +import type {MathTypeIntegrationInstance} from './MathTypeGlobals'; +import type {LexicalEditor, NodeKey} from 'lexical'; +import type {JSX, MutableRefObject} from 'react'; + +import '@wiris/mathtype-generic'; + +import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext'; +import { + $createParagraphNode, + $getNodeByKey, + $getRoot, + $getSelection, + $isRangeSelection, +} from 'lexical'; +import {useCallback, useEffect, useRef} from 'react'; + +import {useMathTypeContext} from './MathTypeContext'; +import {createFormulaFromImage} from './MathTypeData'; +import {getWirisPlugin} from './MathTypeGlobals'; +import {$createMathTypeNode, $isMathTypeNode} from './MathTypeNode'; + +type InsertFormulaResult = { + focusElement: HTMLElement | Window; + node: HTMLImageElement; + windowTarget: Window; +}; + +function $commitFormula( + nodeKey: NodeKey | null, + formula: MathTypeFormula, +): void { + if (nodeKey !== null) { + const node = $getNodeByKey(nodeKey); + if ($isMathTypeNode(node)) { + node.setFormula(formula); + return; + } + } + + const mathTypeNode = $createMathTypeNode(formula); + const selection = $getSelection(); + if ($isRangeSelection(selection)) { + selection.insertNodes([mathTypeNode]); + return; + } + + $getRoot().append($createParagraphNode().append(mathTypeNode)); +} + +function createIntegration( + editor: LexicalEditor, + target: HTMLElement, + toolbar: HTMLElement, + pendingNodeKeyRef: MutableRefObject, +): MathTypeIntegrationInstance { + const wirisPlugin = getWirisPlugin(); + const integration = new wirisPlugin.GenericIntegration({ + target, + toolbar, + }); + wirisPlugin.currentInstance = integration; + + const openNewFormulaEditor = + integration.openNewFormulaEditor.bind(integration); + integration.openNewFormulaEditor = () => { + pendingNodeKeyRef.current = null; + wirisPlugin.currentInstance = integration; + openNewFormulaEditor(); + }; + + integration.insertFormula = ( + focusElement: HTMLElement | Window, + windowTarget: Window, + mathML: string, + wirisProperties: null | object, + ): InsertFormulaResult => { + const image = wirisPlugin.Parser.mathmlToImgObject( + document, + mathML, + wirisProperties, + ); + const formula = createFormulaFromImage(image, mathML); + const nodeKey = pendingNodeKeyRef.current; + pendingNodeKeyRef.current = null; + + editor.update(() => { + $commitFormula(nodeKey, formula); + }); + + integration.core.editionProperties.temporalImage = null; + editor.focus(); + + return {focusElement, node: image, windowTarget}; + }; + + return integration; +} + +export function MathTypePlugin(): JSX.Element { + const [editor] = useLexicalComposerContext(); + const toolbarRef = useRef(null); + const targetRef = useRef(null); + const {integrationRef, pendingNodeKeyRef} = useMathTypeContext(); + + const setRefs = useCallback( + (toolbar: HTMLDivElement | null, target: HTMLDivElement | null) => { + toolbarRef.current = toolbar; + targetRef.current = target; + }, + [], + ); + + useEffect(() => { + const toolbar = toolbarRef.current; + const target = targetRef.current; + if (toolbar === null || target === null) { + return; + } + + const integration = createIntegration( + editor, + target, + toolbar, + pendingNodeKeyRef, + ); + integration.init(); + integration.listeners.fire('onTargetReady', {}); + integrationRef.current = integration; + + return () => { + if (integrationRef.current === integration) { + integrationRef.current = null; + } + toolbar.textContent = ''; + integration.destroy(); + }; + }, [editor, integrationRef, pendingNodeKeyRef]); + + return ( + <> +
{ + setRefs(toolbar, targetRef.current); + }} + /> + + ); } diff --git a/examples/react-mathtype/src/MathTypePlugin.tsx b/examples/react-mathtype/src/MathTypeExtension.tsx similarity index 81% rename from examples/react-mathtype/src/MathTypePlugin.tsx rename to examples/react-mathtype/src/MathTypeExtension.tsx index 450f7ba1f68..b9e75997118 100644 --- a/examples/react-mathtype/src/MathTypePlugin.tsx +++ b/examples/react-mathtype/src/MathTypeExtension.tsx @@ -8,25 +8,33 @@ import type {MathTypeFormula} from './MathTypeData'; import type {MathTypeIntegrationInstance} from './MathTypeGlobals'; +import type {EditorChildrenComponentProps} from '@lexical/react/ReactExtension'; import type {LexicalEditor, NodeKey} from 'lexical'; import type {JSX, MutableRefObject} from 'react'; import '@wiris/mathtype-generic'; import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext'; +import {ReactExtension} from '@lexical/react/ReactExtension'; import { $createParagraphNode, $getNodeByKey, $getRoot, $getSelection, $isRangeSelection, + configExtension, + defineExtension, } from 'lexical'; import {useCallback, useEffect, useRef} from 'react'; -import {useMathTypeContext} from './MathTypeContext'; +import {MathTypeProvider, useMathTypeContext} from './MathTypeContext'; import {createFormulaFromImage} from './MathTypeData'; import {getWirisPlugin} from './MathTypeGlobals'; -import {$createMathTypeNode, $isMathTypeNode} from './MathTypeNode'; +import { + $createMathTypeNode, + $isMathTypeNode, + MathTypeNode, +} from './MathTypeNode'; type InsertFormulaResult = { focusElement: HTMLElement | Window; @@ -105,7 +113,7 @@ function createIntegration( return integration; } -export function MathTypePlugin(): JSX.Element { +function MathTypeIntegrationComponent(): JSX.Element { const [editor] = useLexicalComposerContext(); const toolbarRef = useRef(null); const targetRef = useRef(null); @@ -164,3 +172,26 @@ export function MathTypePlugin(): JSX.Element { ); } + +function MathTypeEditorChildren({ + children, + contentEditable, +}: EditorChildrenComponentProps): JSX.Element { + return ( + + {contentEditable} + {children} + + ); +} + +export const MathTypeExtension = /* @__PURE__ */ defineExtension({ + build: () => ({Component: MathTypeIntegrationComponent}), + dependencies: [ + /* @__PURE__ */ configExtension(ReactExtension, { + EditorChildrenComponent: MathTypeEditorChildren, + }), + ], + name: '@lexical/react-mathtype-example/MathType', + nodes: [MathTypeNode], +});