{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "footnote-node",
  "title": "Footnote Elements",
  "description": "Inline footnote references, definitions, and input UI.",
  "dependencies": [
    "@platejs/footnote"
  ],
  "registryDependencies": [
    "button",
    "command",
    "hover-card",
    "popover",
    "https://platejs.org/r/inline-combobox.json"
  ],
  "files": [
    {
      "path": "src/registry/ui/footnote-node.tsx",
      "content": "'use client';\n\nimport * as React from 'react';\n\nimport type { TFootnoteElement } from '@platejs/footnote';\nimport { PathApi, type Path } from 'platejs';\nimport { FootnoteReferencePlugin } from '@platejs/footnote/react';\nimport type { PlateEditor, PlateElementProps } from 'platejs/react';\n\nimport {\n  PlateElement,\n  useEditorSelector,\n  useFocused,\n  useNavigationHighlight,\n  usePath,\n  useSelected,\n} from 'platejs/react';\n\nimport {\n  HoverCard,\n  HoverCardContent,\n  HoverCardTrigger,\n} from '@/components/ui/hover-card';\nimport {\n  Popover,\n  PopoverAnchor,\n  PopoverContent,\n} from '@/components/ui/popover';\nimport {\n  Command,\n  CommandGroup,\n  CommandItem,\n  CommandList,\n} from '@/components/ui/command';\nimport { cn } from '@/lib/utils';\nimport { Button } from '@/components/ui/button';\nimport {\n  InlineCombobox,\n  InlineComboboxContent,\n  InlineComboboxEmpty,\n  InlineComboboxGroup,\n  InlineComboboxInput,\n  InlineComboboxItem,\n} from '@/registry/ui/inline-combobox';\n\nconst NUMERIC_FOOTNOTE_QUERY = /^\\d+$/;\n\nconst getNavigationAttributes = (\n  attributes: PlateElementProps<TFootnoteElement>['attributes'],\n  navigationHighlight: ReturnType<typeof useNavigationHighlight>\n) => ({\n  ...attributes,\n  'data-nav-cycle': navigationHighlight\n    ? String(navigationHighlight.cycle)\n    : undefined,\n  'data-nav-highlight': navigationHighlight?.variant,\n  'data-nav-pulse': navigationHighlight\n    ? String(navigationHighlight.pulse)\n    : undefined,\n  'data-nav-target': navigationHighlight ? 'true' : undefined,\n  style: {\n    ...(attributes.style as React.CSSProperties | undefined),\n    ['--plate-nav-feedback-duration' as const]: navigationHighlight\n      ? `${navigationHighlight.duration}ms`\n      : undefined,\n  } as React.CSSProperties,\n});\n\nconst getFootnotePreviewLabel = (text?: string) => {\n  const normalized = text?.replace(/\\s+/g, ' ').trim();\n\n  if (!normalized) return 'Empty footnote';\n\n  return normalized.length > 48\n    ? `${normalized.slice(0, 45).trimEnd()}...`\n    : normalized;\n};\n\nconst getReferenceContextLabel = (\n  editor: PlateEditor,\n  path: Path,\n  index: number\n) => {\n  const parentEntry = editor.api.parent(path);\n  const fallback = `Reference ${index + 1}`;\n\n  if (!parentEntry) return fallback;\n\n  const text = editor.api.string(parentEntry[1]);\n  const normalized = text.replace(/\\s+/g, ' ').trim();\n\n  if (!normalized) return fallback;\n\n  return normalized.length > 56\n    ? `${normalized.slice(0, 53).trimEnd()}...`\n    : normalized;\n};\n\nexport function FootnoteReferenceElement(\n  props: PlateElementProps<TFootnoteElement>\n) {\n  const { editor, element } = props;\n  const identifier = element.identifier ?? '';\n  const footnoteApi = editor.getApi(FootnoteReferencePlugin).footnote;\n  const footnoteTransforms = editor.getTransforms(\n    FootnoteReferencePlugin\n  ).footnote;\n  const [hoverOpen, setHoverOpen] = React.useState(false);\n  const focused = useFocused();\n  const path = usePath();\n  const navigationHighlight = useNavigationHighlight(path);\n  const fallbackResolved =\n    identifier && footnoteApi ? footnoteApi.isResolved({ identifier }) : false;\n  const fallbackPreviewText =\n    identifier && footnoteApi\n      ? footnoteApi.definitionText({ identifier })\n      : undefined;\n  const livePreview = useEditorSelector(() => {\n    if (!hoverOpen || !identifier) return null;\n\n    return {\n      isResolved: footnoteApi.isResolved({ identifier }),\n      previewText: footnoteApi.definitionText({ identifier }),\n    };\n  }, [hoverOpen, identifier]);\n  const isResolved = livePreview?.isResolved ?? fallbackResolved;\n  const previewText = livePreview?.previewText ?? fallbackPreviewText;\n  const selected = useSelected();\n  const isSelectionInsideAtom = useEditorSelector(\n    (currentEditor) => {\n      const selection = currentEditor.selection;\n\n      if (!path || !selection) return false;\n\n      return (\n        PathApi.equals(selection.anchor.path, path.concat([0])) &&\n        PathApi.equals(selection.focus.path, path.concat([0])) &&\n        selection.anchor.offset === selection.focus.offset\n      );\n    },\n    [path]\n  );\n\n  return (\n    <PlateElement\n      {...props}\n      as=\"sup\"\n      className=\"group/footnote-ref mx-0.5 align-super\"\n      attributes={{\n        ...getNavigationAttributes(props.attributes, navigationHighlight),\n        contentEditable: false,\n        draggable: true,\n      }}\n    >\n      {props.children}\n      <HoverCard open={hoverOpen} onOpenChange={setHoverOpen} openDelay={150}>\n        <HoverCardTrigger asChild>\n          <button\n            type=\"button\"\n            className={cn(\n              'cursor-pointer rounded-xs font-medium text-primary text-xs focus:ring-2 focus:ring-ring focus:ring-offset-1 group-data-[nav-target=true]/footnote-ref:bg-(--color-highlight)',\n              (selected && focused) || isSelectionInsideAtom\n                ? 'ring-2 ring-ring ring-offset-1'\n                : null\n            )}\n            onClick={(event) => {\n              event.preventDefault();\n              event.stopPropagation();\n            }}\n            onMouseDown={(event) => {\n              if (event.metaKey || event.ctrlKey) {\n                event.preventDefault();\n                event.stopPropagation();\n                if (isResolved) {\n                  footnoteTransforms.focusDefinition({ identifier });\n\n                  return;\n                }\n\n                footnoteTransforms.createDefinition({ identifier });\n              }\n            }}\n          >\n            [{identifier}]\n          </button>\n        </HoverCardTrigger>\n        {previewText ? (\n          <HoverCardContent className=\"w-80\">\n            <div className=\"space-y-1\">\n              <div className=\"text-muted-foreground text-sm leading-relaxed\">\n                {previewText}\n              </div>\n            </div>\n          </HoverCardContent>\n        ) : identifier ? (\n          <HoverCardContent className=\"w-80\">\n            <div className=\"space-y-2\">\n              {isResolved ? (\n                <div className=\"text-sm leading-relaxed\">\n                  No preview available.\n                </div>\n              ) : (\n                <Button\n                  type=\"button\"\n                  variant=\"outline\"\n                  size=\"sm\"\n                  className=\"h-6 rounded-xs px-2 text-[11px]\"\n                  onMouseDown={(event) => {\n                    event.preventDefault();\n                    event.stopPropagation();\n                    footnoteTransforms.createDefinition({ identifier });\n                    setHoverOpen(false);\n                  }}\n                >\n                  Create definition for [^{identifier}]\n                </Button>\n              )}\n            </div>\n          </HoverCardContent>\n        ) : null}\n      </HoverCard>\n    </PlateElement>\n  );\n}\n\nexport function FootnoteDefinitionElement(\n  props: PlateElementProps<TFootnoteElement>\n) {\n  const { editor, element } = props;\n  const identifier = element.identifier ?? '';\n  const footnoteApi = editor.getApi(FootnoteReferencePlugin).footnote;\n  const footnoteTransforms = editor.getTransforms(\n    FootnoteReferencePlugin\n  ).footnote;\n  const path = usePath();\n  const definitionState = useEditorSelector(() => {\n    const isDuplicateDefinition =\n      !!path && !!footnoteApi.isDuplicateDefinition?.({ path });\n    const referenceItems =\n      !isDuplicateDefinition && identifier\n        ? footnoteApi\n            .references({ identifier })\n            .map((entry: any, index: number) => ({\n              index,\n              label: getReferenceContextLabel(editor, entry[1], index),\n            }))\n        : [];\n\n    return {\n      duplicateReplacementIdentifier: isDuplicateDefinition\n        ? footnoteApi.nextId?.()\n        : undefined,\n      isDuplicateDefinition,\n      path,\n      referenceItems,\n    };\n  }, [identifier, path]);\n  const navigationHighlight = useNavigationHighlight(definitionState?.path);\n  const isDuplicateDefinition = !!definitionState?.isDuplicateDefinition;\n  const duplicateReplacementIdentifier =\n    definitionState?.duplicateReplacementIdentifier;\n  const [referencePickerOpen, setReferencePickerOpen] = React.useState(false);\n  const referenceItems = definitionState?.referenceItems ?? [];\n  const hasMultipleReferences = referenceItems.length > 1;\n\n  return (\n    <PlateElement\n      {...props}\n      className={cn(\n        'mt-1.5 flex items-start gap-1.5 data-[nav-target=true]:rounded-md data-[nav-target=true]:bg-(--color-highlight)',\n        isDuplicateDefinition &&\n          'rounded-md border border-amber-500/30 bg-amber-500/5 px-2 py-2'\n      )}\n      attributes={getNavigationAttributes(\n        props.attributes,\n        navigationHighlight\n      )}\n    >\n      <div contentEditable={false}>\n        {isDuplicateDefinition ? (\n          <div className=\"min-w-3 text-amber-700 text-xs tabular-nums\">\n            {identifier}\n          </div>\n        ) : (\n          <Popover\n            open={referencePickerOpen}\n            onOpenChange={setReferencePickerOpen}\n          >\n            <PopoverAnchor asChild>\n              <button\n                type=\"button\"\n                aria-expanded={\n                  hasMultipleReferences ? referencePickerOpen : undefined\n                }\n                aria-haspopup={hasMultipleReferences ? 'dialog' : undefined}\n                aria-label={`Back to reference ${identifier}`}\n                className=\"min-w-3 cursor-pointer rounded-xs text-muted-foreground text-xs tabular-nums underline-offset-2 hover:text-foreground focus:ring-2 focus:ring-ring focus:ring-offset-1\"\n                onClick={(event) => {\n                  event.preventDefault();\n                  event.stopPropagation();\n                }}\n                onMouseDown={(event) => {\n                  event.preventDefault();\n                  event.stopPropagation();\n\n                  if (hasMultipleReferences) {\n                    setReferencePickerOpen((open) => !open);\n\n                    return;\n                  }\n\n                  footnoteTransforms.focusReference({ identifier });\n                }}\n              >\n                {identifier}\n              </button>\n            </PopoverAnchor>\n\n            {hasMultipleReferences && referencePickerOpen ? (\n              <PopoverContent\n                className=\"w-72 p-0\"\n                align=\"start\"\n                sideOffset={8}\n                onCloseAutoFocus={(event) => event.preventDefault()}\n                onOpenAutoFocus={(event) => event.preventDefault()}\n              >\n                <Command>\n                  <CommandList>\n                    <CommandGroup>\n                      {referenceItems.map(\n                        (item: { index: number; label: string }) => (\n                          <CommandItem\n                            key={`${identifier}-${item.index}`}\n                            className=\"cursor-pointer gap-2\"\n                            onMouseDown={(event) => event.preventDefault()}\n                            onSelect={() => {\n                              setReferencePickerOpen(false);\n                              footnoteTransforms.focusReference({\n                                identifier,\n                                index: item.index,\n                              });\n                            }}\n                          >\n                            <span className=\"font-mono text-muted-foreground text-xs\">\n                              {item.index + 1}\n                            </span>\n                            <span className=\"truncate\">{item.label}</span>\n                          </CommandItem>\n                        )\n                      )}\n                    </CommandGroup>\n                  </CommandList>\n                </Command>\n              </PopoverContent>\n            ) : null}\n          </Popover>\n        )}\n      </div>\n      <div className=\"min-w-0 flex-1\">\n        {isDuplicateDefinition ? (\n          <div\n            contentEditable={false}\n            className=\"mb-2 flex flex-wrap items-center gap-2\"\n          >\n            {duplicateReplacementIdentifier && path ? (\n              <Button\n                type=\"button\"\n                variant=\"outline\"\n                size=\"sm\"\n                className=\"h-6 rounded-xs border-amber-500/40 px-2 text-[11px] text-amber-700 hover:bg-amber-500/10 hover:text-amber-800\"\n                onClick={(event) => {\n                  event.preventDefault();\n                  event.stopPropagation();\n                }}\n                onMouseDown={(event) => {\n                  event.preventDefault();\n                  event.stopPropagation();\n                  footnoteTransforms.normalizeDuplicateDefinition({\n                    identifier: duplicateReplacementIdentifier,\n                    path,\n                  });\n                }}\n              >\n                Renumber to [^{duplicateReplacementIdentifier}]\n              </Button>\n            ) : null}\n          </div>\n        ) : null}\n        {props.children}\n      </div>\n    </PlateElement>\n  );\n}\n\nexport function FootnoteInputElement(props: PlateElementProps) {\n  const { editor, element } = props;\n  const [search, setSearch] = React.useState('');\n  const footnoteApi = editor.getApi(FootnoteReferencePlugin).footnote;\n  const insertTransforms = editor.getTransforms(FootnoteReferencePlugin).insert;\n\n  const identifiers = footnoteApi.identifiers?.() ?? [];\n  const nextIdentifier = footnoteApi.nextId?.() ?? '1';\n  const query = search.trim();\n  const numericQuery = NUMERIC_FOOTNOTE_QUERY.test(query) ? query : '';\n  const proposedIdentifier = numericQuery || nextIdentifier;\n  const showCreateOption = !identifiers.includes(proposedIdentifier);\n\n  const filteredIdentifiers = identifiers.filter((identifier: string) => {\n    if (!query) return true;\n\n    const preview = footnoteApi.definitionText?.({ identifier }) ?? '';\n\n    return (\n      identifier.includes(query) ||\n      preview.toLowerCase().includes(query.toLowerCase())\n    );\n  });\n\n  const insertSelectedFootnote = React.useCallback(\n    (identifier: string) => {\n      const before = editor.selection && editor.api.before(editor.selection);\n\n      if (before) {\n        const range = editor.api.range(before, editor.selection);\n\n        if (range && editor.api.string(range) === '[') {\n          editor.tf.deleteBackward('character');\n        }\n      }\n\n      insertTransforms.footnote({\n        focusDefinition: false,\n        identifier,\n      });\n    },\n    [editor, insertTransforms]\n  );\n\n  return (\n    <PlateElement {...props} as=\"span\">\n      <InlineCombobox\n        value={search}\n        element={element}\n        filter={false}\n        setValue={setSearch}\n        trigger=\"^\"\n      >\n        <InlineComboboxInput className=\"min-w-[1ch]\" />\n\n        <InlineComboboxContent className=\"my-1.5 w-72\">\n          {showCreateOption || filteredIdentifiers.length > 0 ? null : (\n            <InlineComboboxEmpty>No footnotes</InlineComboboxEmpty>\n          )}\n\n          <InlineComboboxGroup>\n            {showCreateOption && (!query || numericQuery) ? (\n              <InlineComboboxItem\n                value={`new-${proposedIdentifier}`}\n                onClick={() => insertSelectedFootnote(proposedIdentifier)}\n              >\n                <span className=\"flex min-w-0 items-center gap-1.5 whitespace-nowrap\">\n                  <span className=\"font-mono text-muted-foreground\">\n                    [^{proposedIdentifier}]\n                  </span>\n                  <span className=\"truncate\">: New footnote...</span>\n                </span>\n              </InlineComboboxItem>\n            ) : null}\n\n            {filteredIdentifiers.map((identifier: string) => (\n              <InlineComboboxItem\n                key={identifier}\n                value={`footnote-${identifier}`}\n                onClick={() => insertSelectedFootnote(identifier)}\n              >\n                <span className=\"flex min-w-0 items-center gap-1.5 whitespace-nowrap\">\n                  <span className=\"font-mono text-muted-foreground\">\n                    [^{identifier}]\n                  </span>\n                  <span className=\"truncate\">\n                    :{' '}\n                    {getFootnotePreviewLabel(\n                      footnoteApi.definitionText?.({ identifier })\n                    )}\n                  </span>\n                </span>\n              </InlineComboboxItem>\n            ))}\n          </InlineComboboxGroup>\n        </InlineComboboxContent>\n      </InlineCombobox>\n\n      {props.children}\n    </PlateElement>\n  );\n}\n",
      "type": "registry:ui"
    },
    {
      "path": "src/registry/ui/footnote-node-static.tsx",
      "content": "import * as React from 'react';\n\nimport type { TFootnoteElement } from '@platejs/footnote';\nimport type { SlateElementProps } from 'platejs/static';\n\nimport { SlateElement } from 'platejs/static';\n\nexport function FootnoteReferenceElementStatic(\n  props: SlateElementProps<TFootnoteElement>\n) {\n  const { element } = props;\n\n  return (\n    <SlateElement\n      {...props}\n      as=\"sup\"\n      className=\"mx-0.5 align-super font-medium text-primary text-xs\"\n    >\n      {props.children}[{element.identifier ?? ''}]\n    </SlateElement>\n  );\n}\n\nexport function FootnoteDefinitionElementStatic(\n  props: SlateElementProps<TFootnoteElement>\n) {\n  const { element } = props;\n\n  return (\n    <SlateElement {...props} as=\"div\" className=\"mt-2 flex items-start gap-2\">\n      <div className=\"mt-0.5 min-w-4 text-muted-foreground text-sm tabular-nums\">\n        {element.identifier ?? ''}\n      </div>\n      <div className=\"min-w-0 flex-1\">{props.children}</div>\n    </SlateElement>\n  );\n}\n",
      "type": "registry:ui"
    }
  ],
  "meta": {
    "docs": [
      {
        "route": "/docs/footnote"
      }
    ],
    "examples": [
      "footnote-demo"
    ]
  },
  "type": "registry:ui"
}