{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "equation-node",
  "title": "Equation Element",
  "description": "Displays a LaTeX equation element with an editable popover for inputting and rendering mathematical expressions.",
  "dependencies": [
    "@platejs/math",
    "react-textarea-autosize"
  ],
  "registryDependencies": [
    "popover",
    "https://platejs.org/r/suggestion.json"
  ],
  "files": [
    {
      "path": "src/registry/ui/equation-node.tsx",
      "content": "'use client';\n\nimport * as React from 'react';\nimport TextareaAutosize, {\n  type TextareaAutosizeProps,\n} from 'react-textarea-autosize';\n\nimport type { TEquationElement } from 'platejs';\nimport type { PlateElementProps } from 'platejs/react';\n\nimport { useEquationElement, useEquationInput } from '@platejs/math/react';\nimport { BlockSelectionPlugin } from '@platejs/selection/react';\nimport { CornerDownLeftIcon, RadicalIcon } from 'lucide-react';\nimport {\n  createPrimitiveComponent,\n  PlateElement,\n  useEditorRef,\n  useEditorSelector,\n  useElement,\n  useReadOnly,\n  useSelected,\n} from 'platejs/react';\n\nimport { Button } from '@/components/ui/button';\nimport {\n  Popover,\n  PopoverContent,\n  PopoverTrigger,\n} from '@/components/ui/popover';\nimport { cn } from '@/lib/utils';\nimport { inlineSuggestionVariants } from '@/registry/lib/suggestion';\n\nexport function EquationElement(props: PlateElementProps<TEquationElement>) {\n  const selected = useSelected();\n  const [open, setOpen] = React.useState(selected);\n  const katexRef = React.useRef<HTMLDivElement | null>(null);\n  const lineBreakBadge = (\n    props as PlateElementProps<TEquationElement> & {\n      lineBreakBadge?: React.ReactNode;\n    }\n  ).lineBreakBadge;\n\n  useEquationElement({\n    element: props.element,\n    katexRef,\n    options: {\n      displayMode: true,\n      errorColor: '#cc0000',\n      fleqn: false,\n      leqno: false,\n      macros: { '\\\\f': '#1f(#2)' },\n      output: 'htmlAndMathml',\n      strict: 'warn',\n      throwOnError: false,\n      trust: false,\n    },\n  });\n\n  return (\n    <PlateElement className=\"my-1\" {...props}>\n      <Popover open={open} onOpenChange={setOpen} modal={false}>\n        <PopoverTrigger asChild>\n          <div\n            className={cn(\n              'group flex cursor-pointer select-none items-center justify-center rounded-sm hover:bg-primary/10 data-[selected=true]:bg-primary/10',\n              props.element.texExpression.length === 0\n                ? 'bg-muted p-3 pr-9'\n                : 'px-2 py-1'\n            )}\n            data-selected={selected}\n            contentEditable={false}\n            role=\"button\"\n          >\n            {props.element.texExpression.length > 0 ? (\n              <span ref={katexRef} />\n            ) : (\n              <div className=\"flex h-7 w-full items-center gap-2 whitespace-nowrap text-muted-foreground text-sm\">\n                <RadicalIcon className=\"size-6 text-muted-foreground/80\" />\n                <div>Add a Tex equation</div>\n              </div>\n            )}\n            {lineBreakBadge}\n          </div>\n        </PopoverTrigger>\n\n        <EquationPopoverContent\n          open={open}\n          placeholder={\n            'f(x) = \\\\begin{cases}\\n  x^2, &\\\\quad x > 0 \\\\\\\\\\n  0, &\\\\quad x = 0 \\\\\\\\\\n  -x^2, &\\\\quad x < 0\\n\\\\end{cases}'\n          }\n          isInline={false}\n          setOpen={setOpen}\n        />\n      </Popover>\n\n      {props.children}\n    </PlateElement>\n  );\n}\n\nexport function InlineEquationElement(\n  props: PlateElementProps<TEquationElement>\n) {\n  const { element } = props;\n  const katexRef = React.useRef<HTMLDivElement | null>(null);\n  const selected = useSelected();\n  const isCollapsed = useEditorSelector(\n    (editor) => editor.api.isCollapsed(),\n    []\n  );\n  const [open, setOpen] = React.useState(selected && isCollapsed);\n\n  React.useEffect(() => {\n    if (selected && isCollapsed) {\n      // eslint-disable-next-line react-hooks/set-state-in-effect -- Open the inline equation popover when editor selection enters it.\n      setOpen(true);\n    }\n  }, [selected, isCollapsed]);\n\n  useEquationElement({\n    element,\n    katexRef,\n    options: {\n      displayMode: true,\n      errorColor: '#cc0000',\n      fleqn: false,\n      leqno: false,\n      macros: { '\\\\f': '#1f(#2)' },\n      output: 'htmlAndMathml',\n      strict: 'warn',\n      throwOnError: false,\n      trust: false,\n    },\n  });\n\n  return (\n    <PlateElement\n      {...props}\n      className={cn(\n        'mx-1 inline-block select-none rounded-sm [&_.katex-display]:my-0!'\n      )}\n    >\n      <Popover open={open} onOpenChange={setOpen} modal={false}>\n        <PopoverTrigger asChild>\n          <div\n            className={cn(\n              'after:-top-0.5 after:-left-1 after:absolute after:inset-0 after:z-1 after:h-[calc(100%)+4px] after:w-[calc(100%+8px)] after:rounded-sm after:content-[\"\"]',\n              'h-6',\n              inlineSuggestionVariants(),\n              ((element.texExpression.length > 0 && open) || selected) &&\n                'after:bg-brand/15',\n              element.texExpression.length === 0 &&\n                'text-muted-foreground after:bg-neutral-500/10'\n            )}\n            contentEditable={false}\n          >\n            <span\n              ref={katexRef}\n              className={cn(\n                element.texExpression.length === 0 && 'hidden',\n                'font-mono leading-none'\n              )}\n            />\n            {element.texExpression.length === 0 && (\n              <span>\n                <RadicalIcon className=\"mr-1 inline-block h-[19px] w-4 py-[1.5px] align-text-bottom\" />\n                New equation\n              </span>\n            )}\n          </div>\n        </PopoverTrigger>\n\n        <EquationPopoverContent\n          className=\"my-auto\"\n          open={open}\n          placeholder=\"E = mc^2\"\n          setOpen={setOpen}\n          isInline\n        />\n      </Popover>\n\n      {props.children}\n    </PlateElement>\n  );\n}\n\nconst EquationInput = createPrimitiveComponent(TextareaAutosize)({\n  propsHook: useEquationInput,\n});\n\nconst EquationPopoverContent = ({\n  className,\n  isInline,\n  open,\n  setOpen,\n  ...props\n}: {\n  isInline: boolean;\n  open: boolean;\n  setOpen: (open: boolean) => void;\n} & TextareaAutosizeProps) => {\n  const editor = useEditorRef();\n  const readOnly = useReadOnly();\n  const element = useElement<TEquationElement>();\n\n  React.useEffect(() => {\n    if (isInline && open) {\n      setOpen(true);\n    }\n  }, [isInline, open, setOpen]);\n\n  if (readOnly) return null;\n\n  const onClose = () => {\n    setOpen(false);\n\n    if (isInline) {\n      editor.tf.select(element, { focus: true, next: true });\n    } else {\n      editor\n        .getApi(BlockSelectionPlugin)\n        .blockSelection.set(element.id as string);\n    }\n  };\n\n  return (\n    <PopoverContent\n      className=\"flex gap-2\"\n      onEscapeKeyDown={(e) => {\n        e.preventDefault();\n      }}\n      contentEditable={false}\n    >\n      <EquationInput\n        className={cn('max-h-[50vh] grow resize-none p-2 text-sm', className)}\n        state={{ isInline, open, onClose }}\n        autoFocus\n        {...props}\n      />\n\n      <Button variant=\"secondary\" className=\"px-3\" onClick={onClose}>\n        Done <CornerDownLeftIcon className=\"size-3.5\" />\n      </Button>\n    </PopoverContent>\n  );\n};\n",
      "type": "registry:ui"
    },
    {
      "path": "src/registry/ui/equation-node-static.tsx",
      "content": "import * as React from 'react';\n\nimport type { TEquationElement } from 'platejs';\nimport type { SlateElementProps } from 'platejs/static';\n\nimport { getEquationHtml } from '@platejs/math';\nimport { RadicalIcon } from 'lucide-react';\nimport { SlateElement } from 'platejs/static';\n\nimport { cn } from '@/lib/utils';\nimport { inlineSuggestionVariants } from '@/registry/lib/suggestion';\n\nexport function EquationElementStatic(\n  props: SlateElementProps<TEquationElement>\n) {\n  const { element } = props;\n\n  const html = getEquationHtml({\n    element,\n    options: {\n      displayMode: true,\n      errorColor: '#cc0000',\n      fleqn: false,\n      leqno: false,\n      macros: { '\\\\f': '#1f(#2)' },\n      output: 'htmlAndMathml',\n      strict: 'warn',\n      throwOnError: false,\n      trust: false,\n    },\n  });\n\n  return (\n    <SlateElement className=\"my-1\" {...props}>\n      <div\n        className={cn(\n          'group flex select-none items-center justify-center rounded-sm hover:bg-primary/10 data-[selected=true]:bg-primary/10',\n          element.texExpression.length === 0 ? 'bg-muted p-3 pr-9' : 'px-2 py-1'\n        )}\n      >\n        {element.texExpression.length > 0 ? (\n          <span\n            dangerouslySetInnerHTML={{\n              __html: html,\n            }}\n          />\n        ) : (\n          <div className=\"flex h-7 w-full items-center gap-2 whitespace-nowrap text-muted-foreground text-sm\">\n            <RadicalIcon className=\"size-6 text-muted-foreground/80\" />\n            <div>Add a Tex equation</div>\n          </div>\n        )}\n      </div>\n      {props.children}\n    </SlateElement>\n  );\n}\n\nexport function InlineEquationElementStatic(\n  props: SlateElementProps<TEquationElement>\n) {\n  const html = getEquationHtml({\n    element: props.element,\n    options: {\n      displayMode: true,\n      errorColor: '#cc0000',\n      fleqn: false,\n      leqno: false,\n      macros: { '\\\\f': '#1f(#2)' },\n      output: 'htmlAndMathml',\n      strict: 'warn',\n      throwOnError: false,\n      trust: false,\n    },\n  });\n\n  return (\n    <SlateElement\n      {...props}\n      className=\"inline-block select-none rounded-sm [&_.katex-display]:my-0\"\n    >\n      <div\n        className={cn(\n          'after:-top-0.5 after:-left-1 after:absolute after:inset-0 after:z-1 after:h-[calc(100%)+4px] after:w-[calc(100%+8px)] after:rounded-sm after:content-[\"\"]',\n          'h-6',\n          inlineSuggestionVariants(),\n          props.element.texExpression.length === 0 &&\n            'text-muted-foreground after:bg-neutral-500/10'\n        )}\n      >\n        <span\n          className={cn(\n            props.element.texExpression.length === 0 && 'hidden',\n            'font-mono leading-none'\n          )}\n          dangerouslySetInnerHTML={{ __html: html }}\n        />\n      </div>\n      {props.children}\n    </SlateElement>\n  );\n}\n\n/**\n * DOCX-compatible block equation component.\n * Displays LaTeX source code with styling.\n */\nexport function EquationElementDocx(\n  props: SlateElementProps<TEquationElement>\n) {\n  const { element } = props;\n\n  if (!element.texExpression || element.texExpression.length === 0) {\n    return (\n      <SlateElement {...props}>\n        <p style={{ color: '#888', fontStyle: 'italic' }}>[Empty equation]</p>\n        {props.children}\n      </SlateElement>\n    );\n  }\n\n  return (\n    <SlateElement {...props}>\n      <p\n        style={{\n          fontFamily: 'Cambria Math, Consolas, monospace',\n          fontSize: '12pt',\n          margin: '8pt 0',\n          textAlign: 'center',\n        }}\n      >\n        {element.texExpression}\n      </p>\n      {props.children}\n    </SlateElement>\n  );\n}\n\n/**\n * DOCX-compatible inline equation component.\n * Displays LaTeX source code inline.\n */\nexport function InlineEquationElementDocx(\n  props: SlateElementProps<TEquationElement>\n) {\n  const { element } = props;\n\n  if (!element.texExpression || element.texExpression.length === 0) {\n    return (\n      <SlateElement {...props} as=\"span\">\n        <span style={{ color: '#888', fontStyle: 'italic' }}>[equation]</span>\n        {props.children}\n      </SlateElement>\n    );\n  }\n\n  return (\n    <SlateElement {...props} as=\"span\">\n      <span\n        style={{\n          fontFamily: 'Cambria Math, Consolas, monospace',\n        }}\n      >\n        {element.texExpression}\n      </span>\n      {props.children}\n    </SlateElement>\n  );\n}\n",
      "type": "registry:ui"
    }
  ],
  "meta": {
    "docs": [
      {
        "route": "http://localhost:3000/docs/equation",
        "title": "Equation"
      }
    ],
    "examples": [
      "equation-demo"
    ]
  },
  "type": "registry:ui"
}