{"version":3,"file":"BlocksRenderer.cjs","sources":["../src/BlocksRenderer.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\n\nimport { Block } from './Block';\nimport { type Modifier, type TextInlineNode } from './Text';\n\n/* -------------------------------------------------------------------------------------------------\n * TypeScript types and utils\n * -----------------------------------------------------------------------------------------------*/\n\ninterface LinkInlineNode {\n  type: 'link';\n  url: string;\n  children: TextInlineNode[];\n}\n\ninterface ListItemInlineNode {\n  type: 'list-item';\n  children: DefaultInlineNode[];\n}\n\n// Inline node types\ntype DefaultInlineNode = TextInlineNode | LinkInlineNode;\ntype NonTextInlineNode = Exclude<DefaultInlineNode, TextInlineNode> | ListItemInlineNode;\n\ninterface ParagraphBlockNode {\n  type: 'paragraph';\n  children: DefaultInlineNode[];\n}\n\ninterface QuoteBlockNode {\n  type: 'quote';\n  children: DefaultInlineNode[];\n}\n\ninterface CodeBlockNode {\n  type: 'code';\n  children: DefaultInlineNode[];\n}\n\ninterface HeadingBlockNode {\n  type: 'heading';\n  level: 1 | 2 | 3 | 4 | 5 | 6;\n  children: DefaultInlineNode[];\n}\n\ninterface ListBlockNode {\n  type: 'list';\n  format: 'ordered' | 'unordered';\n  children: (ListItemInlineNode | ListBlockNode)[];\n}\n\ninterface ImageBlockNode {\n  type: 'image';\n  image: {\n    name: string;\n    alternativeText?: string | null;\n    url: string;\n    caption?: string | null;\n    width: number;\n    height: number;\n    formats?: Record<string, unknown>;\n    hash: string;\n    ext: string;\n    mime: string;\n    size: number;\n    previewUrl?: string | null;\n    provider: string;\n    provider_metadata?: unknown | null;\n    createdAt: string;\n    updatedAt: string;\n  };\n  children: [{ type: 'text'; text: '' }];\n}\n\n// Block node types\ntype RootNode =\n  | ParagraphBlockNode\n  | QuoteBlockNode\n  | CodeBlockNode\n  | HeadingBlockNode\n  | ListBlockNode\n  | ImageBlockNode;\ntype Node = RootNode | NonTextInlineNode;\n\n// Util to convert a node to the props of the corresponding React component\ntype GetPropsFromNode<T> = Omit<T, 'type' | 'children'> & {\n  children?: React.ReactNode;\n  // For code blocks, add a plainText property that is created by this renderer\n  plainText?: T extends { type: 'code' } ? string : never;\n};\n\n// Map of all block types to their matching React component\ntype BlocksComponents = {\n  [K in Node['type']]: React.ComponentType<\n    // Find the BlockProps in the union that match the type key of the current BlockNode\n    // and use it as the component props\n    GetPropsFromNode<Extract<Node, { type: K }>>\n  >;\n};\n\n// Map of all inline types to their matching React component\ntype ModifiersComponents = {\n  [K in Modifier]: React.ComponentType<{ children: React.ReactNode }>;\n};\n\n/* -------------------------------------------------------------------------------------------------\n * Default blocks and modifiers components\n * -----------------------------------------------------------------------------------------------*/\n\ninterface ComponentsContextValue {\n  blocks: BlocksComponents;\n  modifiers: ModifiersComponents;\n  missingBlockTypes: string[];\n  missingModifierTypes: string[];\n}\n\nconst defaultComponents: ComponentsContextValue = {\n  blocks: {\n    paragraph: (props) => <p>{props.children}</p>,\n    quote: (props) => <blockquote>{props.children}</blockquote>,\n    code: (props) => (\n      <pre>\n        <code>{props.plainText}</code>\n      </pre>\n    ),\n    heading: ({ level, children }) => {\n      switch (level) {\n        case 1:\n          return <h1>{children}</h1>;\n        case 2:\n          return <h2>{children}</h2>;\n        case 3:\n          return <h3>{children}</h3>;\n        case 4:\n          return <h4>{children}</h4>;\n        case 5:\n          return <h5>{children}</h5>;\n        case 6:\n          return <h6>{children}</h6>;\n      }\n    },\n    link: (props) => <a href={props.url}>{props.children}</a>,\n    list: (props) => {\n      if (props.format === 'ordered') {\n        return <ol>{props.children}</ol>;\n      }\n\n      return <ul>{props.children}</ul>;\n    },\n    'list-item': (props) => <li>{props.children}</li>,\n    image: (props) => <img src={props.image.url} alt={props.image.alternativeText || undefined} />,\n  },\n  modifiers: {\n    bold: (props) => <strong>{props.children}</strong>,\n    italic: (props) => <em>{props.children}</em>,\n    underline: (props) => <u>{props.children}</u>,\n    strikethrough: (props) => <del>{props.children}</del>,\n    code: (props) => <code>{props.children}</code>,\n  },\n  missingBlockTypes: [],\n  missingModifierTypes: [],\n};\n\n/* -------------------------------------------------------------------------------------------------\n * Context to pass blocks and inline components to the nested components\n * -----------------------------------------------------------------------------------------------*/\n\nconst ComponentsContext = React.createContext<ComponentsContextValue>(defaultComponents);\n\ninterface ComponentsProviderProps {\n  children: React.ReactNode;\n  value?: ComponentsContextValue;\n}\n\n// Provide default value so we don't need to import defaultComponents in all tests\nconst ComponentsProvider = ({ children, value = defaultComponents }: ComponentsProviderProps) => {\n  const memoizedValue = React.useMemo(() => value, [value]);\n\n  return <ComponentsContext.Provider value={memoizedValue}>{children}</ComponentsContext.Provider>;\n};\n\nfunction useComponentsContext() {\n  return React.useContext(ComponentsContext);\n}\n\n/* -------------------------------------------------------------------------------------------------\n * BlocksRenderer\n * -----------------------------------------------------------------------------------------------*/\n\ninterface BlocksRendererProps {\n  content: RootNode[];\n  blocks?: Partial<BlocksComponents>;\n  modifiers?: Partial<ModifiersComponents>;\n}\n\nconst BlocksRenderer = (props: BlocksRendererProps) => {\n  // Merge default blocks with the ones provided by the user\n  const blocks = {\n    ...defaultComponents.blocks,\n    ...props.blocks,\n  };\n\n  // Merge default modifiers with the ones provided by the user\n  const modifiers = {\n    ...defaultComponents.modifiers,\n    ...props.modifiers,\n  };\n\n  // Use refs because we can mutate them and avoid triggering re-renders\n  const missingBlockTypes = React.useRef<string[]>([]);\n  const missingModifierTypes = React.useRef<string[]>([]);\n\n  return (\n    <ComponentsProvider\n      value={{\n        blocks,\n        modifiers,\n        missingBlockTypes: missingBlockTypes.current,\n        missingModifierTypes: missingModifierTypes.current,\n      }}\n    >\n      {/* TODO use WeakMap instead of index as the key */}\n      {props.content.map((content, index) => (\n        <Block content={content} key={index} />\n      ))}\n    </ComponentsProvider>\n  );\n};\n\n/* -------------------------------------------------------------------------------------------------\n * Exports\n * -----------------------------------------------------------------------------------------------*/\n\nexport type { RootNode, Node, GetPropsFromNode };\nexport { ComponentsProvider, useComponentsContext, BlocksRenderer };\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAsHA;AAAkD;AACxC;AACmC;AACK;AAI5C;AAGA;AAAe;AAEJ;AAAc;AAEd;AAAc;AAEd;AAAc;AAEd;AAAc;AAEd;AAAc;AAEd;AAAc;AACzB;AACF;AACqD;AAE/C;AACK;AAAoB;AAGtB;AAAoB;AAC7B;AAC4C;AACgD;AAC9F;AACW;AACgC;AACF;AACE;AACM;AACR;AACzC;AACoB;AAEtB;AAMA;AAQA;AACE;AAEA;AACF;AAEA;AACS;AACT;AAYM;AAEJ;AAAe;AACQ;AACZ;AAIX;AAAkB;AACK;AACZ;AAIX;AACA;AAGE;AAAA;AAAC;AAAA;AACQ;AACL;AACA;AACqC;AACM;AAC7C;AAKC;AAAA;AAGP;;;;"}