{"version":3,"file":"RichText.mjs","names":[],"sources":["../../src/RichText.tsx"],"sourcesContent":["import {createElement, Fragment, type ReactNode, useMemo} from 'react';\nimport type {RichTextASTNode} from './RichText.types.js';\nimport {\n  type CustomComponents,\n  RichTextComponents,\n} from './RichText.components.js';\n\nexport interface RichTextPropsBase<ComponentGeneric extends React.ElementType> {\n  /** An HTML tag or React Component to be rendered as the base element wrapper. The default is `div`. */\n  as?: ComponentGeneric;\n  /** The JSON string that correspond to the Storefront API's [RichText format](https://shopify.dev/docs/apps/custom-data/metafields/types#rich-text-formatting). */\n  data: string;\n  /** Customize how rich text components are rendered */\n  components?: CustomComponents;\n  /** Remove rich text formatting and render plain text */\n  plain?: boolean;\n}\n\n/** @publicDocs */\nexport function RichText<ComponentGeneric extends React.ElementType = 'div'>({\n  as,\n  data,\n  plain,\n  components,\n  ...passthroughProps\n}: RichTextProps<ComponentGeneric>): JSX.Element {\n  try {\n    const Wrapper = as ?? 'div';\n    const parsedData = useMemo(\n      () => JSON.parse(data) as RichTextASTNode,\n      [data],\n    );\n\n    return (\n      <Wrapper {...passthroughProps}>\n        {plain\n          ? richTextToString(parsedData)\n          : serializeRichTextASTNode(components, parsedData)}\n      </Wrapper>\n    );\n  } catch (e) {\n    throw new Error(\n      '[h2:error:RichText] Parsing error. Make sure to pass a JSON string of rich text metafield',\n      {\n        cause: e,\n      },\n    );\n  }\n}\n\n// This article helps understand the typing here https://www.benmvp.com/blog/polymorphic-react-components-typescript/ Ben is the best :)\nexport type RichTextProps<ComponentGeneric extends React.ElementType> =\n  RichTextPropsBase<ComponentGeneric> &\n    Omit<\n      React.ComponentPropsWithoutRef<ComponentGeneric>,\n      keyof RichTextPropsBase<ComponentGeneric>\n    >;\n\nfunction serializeRichTextASTNode(\n  components: CustomComponents = {},\n  node: RichTextASTNode,\n  index = 0,\n): ReactNode {\n  let children;\n  if ('children' in node) {\n    children = node.children.map((child, childIndex) =>\n      serializeRichTextASTNode(components, child, childIndex),\n    );\n  }\n\n  const Component =\n    components[node.type === 'list-item' ? 'listItem' : node.type] ??\n    RichTextComponents[node.type];\n\n  switch (node.type) {\n    case 'root':\n      return createElement(\n        Component as Exclude<CustomComponents['root'], undefined>,\n        {\n          key: index,\n          node: {\n            type: 'root',\n            children,\n          },\n        },\n      );\n    case 'heading':\n      return createElement(\n        Component as Exclude<CustomComponents['heading'], undefined>,\n        {\n          key: index,\n          node: {\n            type: 'heading',\n            level: node.level,\n            children,\n          },\n        },\n      );\n    case 'paragraph':\n      return createElement(\n        Component as Exclude<CustomComponents['paragraph'], undefined>,\n        {\n          key: index,\n          node: {\n            type: 'paragraph',\n            children,\n          },\n        },\n      );\n    case 'text': {\n      const elements = (node.value ?? '')\n        .split('\\n')\n        .flatMap((value, subindex) => {\n          const key = `${index}-${value}-${subindex}`;\n          const textElement = createElement(\n            Component as Exclude<CustomComponents['text'], undefined>,\n            {\n              key,\n              node: {\n                type: 'text',\n                italic: node.italic,\n                bold: node.bold,\n                value,\n              },\n            },\n          );\n\n          // Add a `<br>` before each substring except the first one\n          return subindex === 0\n            ? textElement\n            : [createElement('br', {key: `${key}-br`}), textElement];\n        });\n\n      return elements.length > 1\n        ? createElement(Fragment, {key: index}, elements)\n        : elements[0];\n    }\n    case 'link':\n      return createElement(\n        Component as Exclude<CustomComponents['link'], undefined>,\n        {\n          key: index,\n          node: {\n            type: 'link',\n            url: node.url,\n            title: node.title,\n            target: node.target,\n            children,\n          },\n        },\n      );\n    case 'list':\n      return createElement(\n        Component as Exclude<CustomComponents['list'], undefined>,\n        {\n          key: index,\n          node: {\n            type: 'list',\n            listType: node.listType,\n            children,\n          },\n        },\n      );\n    case 'list-item':\n      return createElement(\n        Component as Exclude<CustomComponents['listItem'], undefined>,\n        {\n          key: index,\n          node: {\n            type: 'list-item',\n            children,\n          },\n        },\n      );\n  }\n}\n\nfunction richTextToString(\n  node: RichTextASTNode,\n  result: string[] = [],\n): string {\n  switch (node.type) {\n    case 'root':\n      node.children.forEach((child) => richTextToString(child, result));\n      break;\n    case 'heading':\n    case 'paragraph':\n      node.children.forEach((child) => richTextToString(child, result));\n      result.push(' ');\n      break;\n    case 'text':\n      result.push(node.value || '');\n      break;\n    case 'link':\n      node.children.forEach((child) => richTextToString(child, result));\n      break;\n    case 'list':\n      node.children.forEach((item) => {\n        if (item.children) {\n          item.children.forEach((child) => richTextToString(child, result));\n        }\n        result.push(' ');\n      });\n      break;\n    default:\n      throw new Error(`Unknown node encountered ${node.type}`);\n  }\n\n  return result.join('').trim();\n}\n\n// This is only for documentation purposes, and it is not used in the code.\n/** @publicDocs */\nexport type RichTextPropsForDocs<AsType extends React.ElementType = 'div'> =\n  RichTextPropsBase<AsType>;\n"],"mappings":";;;;;AAmBA,SAAgB,SAA6D,EAC3E,IACA,MACA,OACA,YACA,GAAG,oBAC4C;AAC/C,KAAI;EACF,MAAM,UAAU,MAAM;EACtB,MAAM,aAAa,cACX,KAAK,MAAM,KAAK,EACtB,CAAC,KAAK,CACP;AAED,SACE,oBAAC,SAAD;GAAS,GAAI;aACV,QACG,iBAAiB,WAAW,GAC5B,yBAAyB,YAAY,WAAW;GAC5C,CAAA;UAEL,GAAG;AACV,QAAM,IAAI,MACR,6FACA,EACE,OAAO,GACR,CACF;;;AAYL,SAAS,yBACP,aAA+B,EAAE,EACjC,MACA,QAAQ,GACG;CACX,IAAI;AACJ,KAAI,cAAc,KAChB,YAAW,KAAK,SAAS,KAAK,OAAO,eACnC,yBAAyB,YAAY,OAAO,WAAW,CACxD;CAGH,MAAM,YACJ,WAAW,KAAK,SAAS,cAAc,aAAa,KAAK,SACzD,mBAAmB,KAAK;AAE1B,SAAQ,KAAK,MAAb;EACE,KAAK,OACH,QAAO,cACL,WACA;GACE,KAAK;GACL,MAAM;IACJ,MAAM;IACN;IACD;GACF,CACF;EACH,KAAK,UACH,QAAO,cACL,WACA;GACE,KAAK;GACL,MAAM;IACJ,MAAM;IACN,OAAO,KAAK;IACZ;IACD;GACF,CACF;EACH,KAAK,YACH,QAAO,cACL,WACA;GACE,KAAK;GACL,MAAM;IACJ,MAAM;IACN;IACD;GACF,CACF;EACH,KAAK,QAAQ;GACX,MAAM,YAAY,KAAK,SAAS,IAC7B,MAAM,KAAK,CACX,SAAS,OAAO,aAAa;IAC5B,MAAM,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG;IACjC,MAAM,cAAc,cAClB,WACA;KACE;KACA,MAAM;MACJ,MAAM;MACN,QAAQ,KAAK;MACb,MAAM,KAAK;MACX;MACD;KACF,CACF;AAGD,WAAO,aAAa,IAChB,cACA,CAAC,cAAc,MAAM,EAAC,KAAK,GAAG,IAAI,MAAK,CAAC,EAAE,YAAY;KAC1D;AAEJ,UAAO,SAAS,SAAS,IACrB,cAAc,UAAU,EAAC,KAAK,OAAM,EAAE,SAAS,GAC/C,SAAS;;EAEf,KAAK,OACH,QAAO,cACL,WACA;GACE,KAAK;GACL,MAAM;IACJ,MAAM;IACN,KAAK,KAAK;IACV,OAAO,KAAK;IACZ,QAAQ,KAAK;IACb;IACD;GACF,CACF;EACH,KAAK,OACH,QAAO,cACL,WACA;GACE,KAAK;GACL,MAAM;IACJ,MAAM;IACN,UAAU,KAAK;IACf;IACD;GACF,CACF;EACH,KAAK,YACH,QAAO,cACL,WACA;GACE,KAAK;GACL,MAAM;IACJ,MAAM;IACN;IACD;GACF,CACF;;;AAIP,SAAS,iBACP,MACA,SAAmB,EAAE,EACb;AACR,SAAQ,KAAK,MAAb;EACE,KAAK;AACH,QAAK,SAAS,SAAS,UAAU,iBAAiB,OAAO,OAAO,CAAC;AACjE;EACF,KAAK;EACL,KAAK;AACH,QAAK,SAAS,SAAS,UAAU,iBAAiB,OAAO,OAAO,CAAC;AACjE,UAAO,KAAK,IAAI;AAChB;EACF,KAAK;AACH,UAAO,KAAK,KAAK,SAAS,GAAG;AAC7B;EACF,KAAK;AACH,QAAK,SAAS,SAAS,UAAU,iBAAiB,OAAO,OAAO,CAAC;AACjE;EACF,KAAK;AACH,QAAK,SAAS,SAAS,SAAS;AAC9B,QAAI,KAAK,SACP,MAAK,SAAS,SAAS,UAAU,iBAAiB,OAAO,OAAO,CAAC;AAEnE,WAAO,KAAK,IAAI;KAChB;AACF;EACF,QACE,OAAM,IAAI,MAAM,4BAA4B,KAAK,OAAO;;AAG5D,QAAO,OAAO,KAAK,GAAG,CAAC,MAAM"}