{"version":3,"file":"RichText.mjs","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\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.\nexport type RichTextPropsForDocs<AsType extends React.ElementType = 'div'> =\n  RichTextPropsBase<AsType>;\n"],"names":[],"mappings":";;;AAkBO,SAAS,SAA6D;AAAA,EAC3E;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAiD;AAC/C,MAAI;AACF,UAAM,UAAU,MAAM;AACtB,UAAM,aAAa;AAAA,MACjB,MAAM,KAAK,MAAM,IAAI;AAAA,MACrB,CAAC,IAAI;AAAA,IAAA;AAGP,WACE,oBAAC,SAAA,EAAS,GAAG,kBACV,UAAA,QACG,iBAAiB,UAAU,IAC3B,yBAAyB,YAAY,UAAU,EAAA,CACrD;AAAA,EAEJ,SAAS,GAAG;AACV,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,QACE,OAAO;AAAA,MAAA;AAAA,IACT;AAAA,EAEJ;AACF;AAUA,SAAS,yBACP,aAA+B,CAAA,GAC/B,MACA,QAAQ,GACG;AACX,MAAI;AACJ,MAAI,cAAc,MAAM;AACtB,eAAW,KAAK,SAAS;AAAA,MAAI,CAAC,OAAO,eACnC,yBAAyB,YAAY,OAAO,UAAU;AAAA,IAAA;AAAA,EAE1D;AAEA,QAAM,YACJ,WAAW,KAAK,SAAS,cAAc,aAAa,KAAK,IAAI,KAC7D,mBAAmB,KAAK,IAAI;AAE9B,UAAQ,KAAK,MAAA;AAAA,IACX,KAAK;AACH,aAAO;AAAA,QACL;AAAA,QACA;AAAA,UACE,KAAK;AAAA,UACL,MAAM;AAAA,YACJ,MAAM;AAAA,YACN;AAAA,UAAA;AAAA,QACF;AAAA,MACF;AAAA,IAEJ,KAAK;AACH,aAAO;AAAA,QACL;AAAA,QACA;AAAA,UACE,KAAK;AAAA,UACL,MAAM;AAAA,YACJ,MAAM;AAAA,YACN,OAAO,KAAK;AAAA,YACZ;AAAA,UAAA;AAAA,QACF;AAAA,MACF;AAAA,IAEJ,KAAK;AACH,aAAO;AAAA,QACL;AAAA,QACA;AAAA,UACE,KAAK;AAAA,UACL,MAAM;AAAA,YACJ,MAAM;AAAA,YACN;AAAA,UAAA;AAAA,QACF;AAAA,MACF;AAAA,IAEJ,KAAK,QAAQ;AACX,YAAM,YAAY,KAAK,SAAS,IAC7B,MAAM,IAAI,EACV,QAAQ,CAAC,OAAO,aAAa;AAC5B,cAAM,MAAM,GAAG,KAAK,IAAI,KAAK,IAAI,QAAQ;AACzC,cAAM,cAAc;AAAA,UAClB;AAAA,UACA;AAAA,YACE;AAAA,YACA,MAAM;AAAA,cACJ,MAAM;AAAA,cACN,QAAQ,KAAK;AAAA,cACb,MAAM,KAAK;AAAA,cACX;AAAA,YAAA;AAAA,UACF;AAAA,QACF;AAIF,eAAO,aAAa,IAChB,cACA,CAAC,cAAc,MAAM,EAAC,KAAK,GAAG,GAAG,MAAA,CAAM,GAAG,WAAW;AAAA,MAC3D,CAAC;AAEH,aAAO,SAAS,SAAS,IACrB,cAAc,UAAU,EAAC,KAAK,MAAA,GAAQ,QAAQ,IAC9C,SAAS,CAAC;AAAA,IAChB;AAAA,IACA,KAAK;AACH,aAAO;AAAA,QACL;AAAA,QACA;AAAA,UACE,KAAK;AAAA,UACL,MAAM;AAAA,YACJ,MAAM;AAAA,YACN,KAAK,KAAK;AAAA,YACV,OAAO,KAAK;AAAA,YACZ,QAAQ,KAAK;AAAA,YACb;AAAA,UAAA;AAAA,QACF;AAAA,MACF;AAAA,IAEJ,KAAK;AACH,aAAO;AAAA,QACL;AAAA,QACA;AAAA,UACE,KAAK;AAAA,UACL,MAAM;AAAA,YACJ,MAAM;AAAA,YACN,UAAU,KAAK;AAAA,YACf;AAAA,UAAA;AAAA,QACF;AAAA,MACF;AAAA,IAEJ,KAAK;AACH,aAAO;AAAA,QACL;AAAA,QACA;AAAA,UACE,KAAK;AAAA,UACL,MAAM;AAAA,YACJ,MAAM;AAAA,YACN;AAAA,UAAA;AAAA,QACF;AAAA,MACF;AAAA,EACF;AAEN;AAEA,SAAS,iBACP,MACA,SAAmB,IACX;AACR,UAAQ,KAAK,MAAA;AAAA,IACX,KAAK;AACH,WAAK,SAAS,QAAQ,CAAC,UAAU,iBAAiB,OAAO,MAAM,CAAC;AAChE;AAAA,IACF,KAAK;AAAA,IACL,KAAK;AACH,WAAK,SAAS,QAAQ,CAAC,UAAU,iBAAiB,OAAO,MAAM,CAAC;AAChE,aAAO,KAAK,GAAG;AACf;AAAA,IACF,KAAK;AACH,aAAO,KAAK,KAAK,SAAS,EAAE;AAC5B;AAAA,IACF,KAAK;AACH,WAAK,SAAS,QAAQ,CAAC,UAAU,iBAAiB,OAAO,MAAM,CAAC;AAChE;AAAA,IACF,KAAK;AACH,WAAK,SAAS,QAAQ,CAAC,SAAS;AAC9B,YAAI,KAAK,UAAU;AACjB,eAAK,SAAS,QAAQ,CAAC,UAAU,iBAAiB,OAAO,MAAM,CAAC;AAAA,QAClE;AACA,eAAO,KAAK,GAAG;AAAA,MACjB,CAAC;AACD;AAAA,IACF;AACE,YAAM,IAAI,MAAM,4BAA4B,KAAK,IAAI,EAAE;AAAA,EAAA;AAG3D,SAAO,OAAO,KAAK,EAAE,EAAE,KAAA;AACzB;"}