@kushagradhawan/kookie-blocks 0.1.39 → 0.1.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"streaming-markdown.d.ts","sourceRoot":"","sources":["../../../../src/components/markdown/streaming-markdown.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAwC,MAAM,OAAO,CAAC;AAC7D,OAAsB,EAAE,KAAK,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAOhE,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAa3D;;GAEG;AACH,MAAM,MAAM,wBAAwB,GAAG,wBAAwB,GAAG;IAChE;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;;;OAIG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAC;IAEjC;;;;OAIG;IACH,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,KAAK,CAAC;QAAE,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAE3D;;OAEG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;CAClC,CAAC;AAEF,KAAK,sBAAsB,GAAG;IAC5B;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAEhB;;OAEG;IACH,EAAE,EAAE,MAAM,CAAC;IAEX;;OAEG;IACH,OAAO,CAAC,EAAE,wBAAwB,CAAC;CACpC,CAAC;AA6CF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,OAAY,EAAE,EAAE,sBAAsB,4BA2CtF"}
1
+ {"version":3,"file":"streaming-markdown.d.ts","sourceRoot":"","sources":["../../../../src/components/markdown/streaming-markdown.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAwC,MAAM,OAAO,CAAC;AAC7D,OAAsB,EAAE,KAAK,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAOhE,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAa3D;;GAEG;AACH,MAAM,MAAM,wBAAwB,GAAG,wBAAwB,GAAG;IAChE;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;;;OAIG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAC;IAEjC;;;;OAIG;IACH,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,KAAK,CAAC;QAAE,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAE3D;;OAEG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;CAClC,CAAC;AAEF,KAAK,sBAAsB,GAAG;IAC5B;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAEhB;;OAEG;IACH,EAAE,EAAE,MAAM,CAAC;IAEX;;OAEG;IACH,OAAO,CAAC,EAAE,wBAAwB,CAAC;CACpC,CAAC;AA6CF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,OAAY,EAAE,EAAE,sBAAsB,4BA2DtF"}
@@ -1,2 +1,2 @@
1
- "use strict";"use client";var B=Object.create;var d=Object.defineProperty;var I=Object.getOwnPropertyDescriptor;var S=Object.getOwnPropertyNames;var E=Object.getPrototypeOf,x=Object.prototype.hasOwnProperty;var L=(o,n)=>{for(var t in n)d(o,t,{get:n[t],enumerable:!0})},O=(o,n,t,i)=>{if(n&&typeof n=="object"||typeof n=="function")for(let r of S(n))!x.call(o,r)&&r!==t&&d(o,r,{get:()=>n[r],enumerable:!(i=I(n,r))||i.enumerable});return o};var s=(o,n,t)=>(t=o!=null?B(E(o)):{},O(n||!o||!o.__esModule?d(t,"default",{value:o,enumerable:!0}):t,o)),A=o=>O(d({},"__esModule",{value:!0}),o);var T={};L(T,{StreamingMarkdown:()=>H});module.exports=A(T);var e=s(require("react")),P=s(require("react-markdown")),h=s(require("remark-gfm")),y=s(require("rehype-raw")),p=s(require("harden-react-markdown")),l=require("@kushagradhawan/kookie-ui"),C=require("./create-markdown-components.js"),c=require("./utils/markdown-streaming.js");const F=typeof p.default=="function"?p.default:p.default.default||p.default,R=F(P.default),_=["https://","http://","/"],D=["https://","http://","/","data:"],N=["mailto:","tel:","data:","http:","https:"],G=typeof window<"u"&&window.location?.origin?window.location.origin:"https://app.kookie.ai";function z(o){return o||G}const w=(0,e.memo)(({content:o,defaultOrigin:n,components:t})=>e.default.createElement(l.Box,{width:"100%"},e.default.createElement(R,{defaultOrigin:n,allowedLinkPrefixes:_,allowedImagePrefixes:D,allowedProtocols:N,allowDataImages:!0,components:t,remarkPlugins:[h.default],rehypePlugins:[y.default]},o)),(o,n)=>o.content===n.content&&o.defaultOrigin===n.defaultOrigin&&o.components===n.components);w.displayName="MarkdownBlock";function H({content:o,id:n,options:t={}}){const{defaultOrigin:i,enableBlockMemoization:r=!0,blockParser:k,components:f={},...g}=t,u=(0,e.useMemo)(()=>z(i),[i]),M=(0,e.useMemo)(()=>({...(0,C.createMarkdownComponents)(g),...f}),[g,f]),m=(0,e.useMemo)(()=>{if(!r||!k){const a=(0,c.completeUnterminatedMarkdown)(o);return a.trim()?[a]:[]}return(0,c.parseMarkdownIntoBlocks)(o,k)},[o,r,k]);return m.length?m.length===1?e.default.createElement(w,{content:m[0],defaultOrigin:u,components:M}):e.default.createElement(l.Flex,{direction:"column",gap:"2",width:"100%"},m.map((a,b)=>e.default.createElement(w,{key:`${n}-block-${b}`,content:a,defaultOrigin:u,components:M}))):null}
1
+ "use strict";"use client";var E=Object.create;var l=Object.defineProperty;var x=Object.getOwnPropertyDescriptor;var L=Object.getOwnPropertyNames;var A=Object.getPrototypeOf,F=Object.prototype.hasOwnProperty;var R=(o,n)=>{for(var t in n)l(o,t,{get:n[t],enumerable:!0})},y=(o,n,t,i)=>{if(n&&typeof n=="object"||typeof n=="function")for(let r of L(n))!F.call(o,r)&&r!==t&&l(o,r,{get:()=>n[r],enumerable:!(i=x(n,r))||i.enumerable});return o};var s=(o,n,t)=>(t=o!=null?E(A(o)):{},y(n||!o||!o.__esModule?l(t,"default",{value:o,enumerable:!0}):t,o)),_=o=>y(l({},"__esModule",{value:!0}),o);var $={};R($,{StreamingMarkdown:()=>X});module.exports=_($);var e=s(require("react")),C=s(require("react-markdown")),b=s(require("remark-gfm")),B=s(require("rehype-raw")),p=s(require("harden-react-markdown")),d=require("@kushagradhawan/kookie-ui"),I=require("./create-markdown-components.js"),c=require("./utils/markdown-streaming.js");const D=typeof p.default=="function"?p.default:p.default.default||p.default,N=D(C.default),G=["https://","http://","/"],H=["https://","http://","/","data:"],z=["mailto:","tel:","data:","http:","https:"],T=typeof window<"u"&&window.location?.origin?window.location.origin:"https://app.kookie.ai";function U(o){return o||T}const g=(0,e.memo)(({content:o,defaultOrigin:n,components:t})=>e.default.createElement(d.Box,{width:"100%"},e.default.createElement(N,{defaultOrigin:n,allowedLinkPrefixes:G,allowedImagePrefixes:H,allowedProtocols:z,allowDataImages:!0,components:t,remarkPlugins:[b.default],rehypePlugins:[B.default]},o)),(o,n)=>o.content===n.content&&o.defaultOrigin===n.defaultOrigin&&o.components===n.components);g.displayName="MarkdownBlock";function X({content:o,id:n,options:t={}}){const{defaultOrigin:i,enableBlockMemoization:r=!0,blockParser:k,components:f,codeBlockCollapsible:w=!1,imageComponent:u,inlineCodeHighContrast:M=!0,spacing:O="spacious"}=t,P=(0,e.useMemo)(()=>U(i),[i]),h=(0,e.useMemo)(()=>({...(0,I.createMarkdownComponents)({codeBlockCollapsible:w,imageComponent:u,inlineCodeHighContrast:M,spacing:O}),...f}),[w,u,M,O,f]),m=(0,e.useMemo)(()=>{if(!r||!k){const a=(0,c.completeUnterminatedMarkdown)(o);return a.trim()?[a]:[]}return(0,c.parseMarkdownIntoBlocks)(o,k)},[o,r,k]);return m.length?m.length===1?e.default.createElement(g,{content:m[0],defaultOrigin:P,components:h}):e.default.createElement(d.Flex,{direction:"column",gap:"2",width:"100%"},m.map((a,S)=>e.default.createElement(g,{key:`${n}-block-${S}`,content:a,defaultOrigin:P,components:h}))):null}
2
2
  //# sourceMappingURL=streaming-markdown.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/components/markdown/streaming-markdown.tsx"],
4
- "sourcesContent": ["\"use client\";\n\nimport React, { memo, useMemo, type ReactNode } from \"react\";\nimport ReactMarkdown, { type Components } from \"react-markdown\";\nimport remarkGfm from \"remark-gfm\";\nimport rehypeRaw from \"rehype-raw\";\nimport hardenReactMarkdownModule from \"harden-react-markdown\";\nimport { Box, Flex } from \"@kushagradhawan/kookie-ui\";\nimport { createMarkdownComponents } from \"./create-markdown-components.js\";\nimport { completeUnterminatedMarkdown, parseMarkdownIntoBlocks } from \"./utils/markdown-streaming.js\";\nimport type { MarkdownComponentOptions } from \"./types.js\";\n\n// Handle different export formats\nconst hardenReactMarkdown =\n typeof hardenReactMarkdownModule === \"function\" ? hardenReactMarkdownModule : (hardenReactMarkdownModule as any).default || hardenReactMarkdownModule;\n\nconst HardenedMarkdown = hardenReactMarkdown(ReactMarkdown);\n\nconst LINK_PREFIXES = [\"https://\", \"http://\", \"/\"];\nconst IMAGE_PREFIXES = [\"https://\", \"http://\", \"/\", \"data:\"];\nconst ALLOWED_PROTOCOLS = [\"mailto:\", \"tel:\", \"data:\", \"http:\", \"https:\"];\nconst DEFAULT_APP_ORIGIN = typeof window !== \"undefined\" && window.location?.origin ? window.location.origin : \"https://app.kookie.ai\";\n\n/**\n * Options for StreamingMarkdown component\n */\nexport type StreamingMarkdownOptions = MarkdownComponentOptions & {\n /**\n * Security origin for link/image validation\n * @default window.location.origin or \"https://app.kookie.ai\"\n */\n defaultOrigin?: string;\n\n /**\n * Whether to enable block-level memoization for performance\n * Recommended for streaming scenarios where content updates frequently\n * @default true\n */\n enableBlockMemoization?: boolean;\n\n /**\n * Custom parser for splitting content into blocks\n * If not provided, content will be rendered as a single block\n * For optimal streaming performance, use marked.lexer with GFM enabled\n */\n blockParser?: (content: string) => Array<{ raw?: string }>;\n\n /**\n * Override default component mappings\n */\n components?: Partial<Components>;\n};\n\ntype StreamingMarkdownProps = {\n /**\n * Markdown content to render (supports streaming/incomplete markdown)\n */\n content: string;\n\n /**\n * Unique identifier for this markdown instance (used for keys)\n */\n id: string;\n\n /**\n * Optional configuration\n */\n options?: StreamingMarkdownOptions;\n};\n\ntype MarkdownBlockProps = {\n content: string;\n defaultOrigin: string;\n components: Components;\n};\n\n/**\n * Resolves the default origin for security validation\n */\nfunction resolveDefaultOrigin(customOrigin?: string): string {\n if (customOrigin) {\n return customOrigin;\n }\n return DEFAULT_APP_ORIGIN;\n}\n\n/**\n * Memoized markdown block component for efficient streaming rendering\n */\nconst MarkdownBlock = memo(\n ({ content, defaultOrigin, components }: MarkdownBlockProps) => {\n return (\n <Box width=\"100%\">\n <HardenedMarkdown\n defaultOrigin={defaultOrigin}\n allowedLinkPrefixes={LINK_PREFIXES}\n allowedImagePrefixes={IMAGE_PREFIXES}\n allowedProtocols={ALLOWED_PROTOCOLS}\n allowDataImages\n components={components}\n remarkPlugins={[remarkGfm]}\n rehypePlugins={[rehypeRaw]}\n >\n {content}\n </HardenedMarkdown>\n </Box>\n );\n },\n (previous, next) => previous.content === next.content && previous.defaultOrigin === next.defaultOrigin && previous.components === next.components\n);\n\nMarkdownBlock.displayName = \"MarkdownBlock\";\n\n/**\n * StreamingMarkdown - A drop-in markdown renderer designed for AI streaming.\n *\n * Features:\n * - Unterminated block parsing (handles incomplete markdown during streaming)\n * - Block-level memoization for performance\n * - Security hardening (validates links/images)\n * - GitHub Flavored Markdown support\n * - KookieUI component integration\n * - Code syntax highlighting via CodeBlock\n *\n * @example\n * ```tsx\n * import { StreamingMarkdown } from '@kushagradhawan/kookie-blocks';\n * import { marked } from 'marked';\n *\n * function ChatMessage({ message }) {\n * return (\n * <StreamingMarkdown\n * content={message.content}\n * id={message.id}\n * options={{\n * blockParser: (content) => marked.lexer(content, { gfm: true }),\n * enableBlockMemoization: true,\n * }}\n * />\n * );\n * }\n * ```\n */\nexport function StreamingMarkdown({ content, id, options = {} }: StreamingMarkdownProps) {\n const { defaultOrigin: customOrigin, enableBlockMemoization = true, blockParser, components: customComponents = {}, ...componentOptions } = options;\n\n // Resolve security origin\n const defaultOrigin = useMemo(() => resolveDefaultOrigin(customOrigin), [customOrigin]);\n\n // Create component mappings with custom overrides\n const markdownComponents = useMemo(() => {\n const baseComponents = createMarkdownComponents(componentOptions);\n return {\n ...baseComponents,\n ...customComponents,\n };\n }, [componentOptions, customComponents]);\n\n // Parse content into blocks for memoization (if enabled and parser provided)\n const blocks = useMemo(() => {\n if (!enableBlockMemoization || !blockParser) {\n // No block splitting - just complete unterminated markdown\n const completed = completeUnterminatedMarkdown(content);\n return completed.trim() ? [completed] : [];\n }\n\n return parseMarkdownIntoBlocks(content, blockParser);\n }, [content, enableBlockMemoization, blockParser]);\n\n if (!blocks.length) {\n return null;\n }\n\n // Single block - no need for wrapper\n if (blocks.length === 1) {\n return <MarkdownBlock content={blocks[0]} defaultOrigin={defaultOrigin} components={markdownComponents} />;\n }\n\n // Multiple blocks - render with flex wrapper\n return (\n <Flex direction=\"column\" gap=\"2\" width=\"100%\">\n {blocks.map((block, index) => (\n <MarkdownBlock key={`${id}-block-${index}`} content={block} defaultOrigin={defaultOrigin} components={markdownComponents} />\n ))}\n </Flex>\n );\n}\n"],
5
- "mappings": "ukBAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,uBAAAE,IAAA,eAAAC,EAAAH,GAEA,IAAAI,EAAqD,oBACrDC,EAA+C,6BAC/CC,EAAsB,yBACtBC,EAAsB,yBACtBC,EAAsC,oCACtCC,EAA0B,qCAC1BC,EAAyC,2CACzCC,EAAsE,yCAItE,MAAMC,EACJ,OAAO,EAAAC,SAA8B,WAAa,EAAAA,QAA6B,EAAAA,QAAkC,SAAW,EAAAA,QAExHC,EAAmBF,EAAoB,EAAAG,OAAa,EAEpDC,EAAgB,CAAC,WAAY,UAAW,GAAG,EAC3CC,EAAiB,CAAC,WAAY,UAAW,IAAK,OAAO,EACrDC,EAAoB,CAAC,UAAW,OAAQ,QAAS,QAAS,QAAQ,EAClEC,EAAqB,OAAO,OAAW,KAAe,OAAO,UAAU,OAAS,OAAO,SAAS,OAAS,wBA0D/G,SAASC,EAAqBC,EAA+B,CAC3D,OAAIA,GAGGF,CACT,CAKA,MAAMG,KAAgB,QACpB,CAAC,CAAE,QAAAC,EAAS,cAAAC,EAAe,WAAAC,CAAW,IAElC,EAAAC,QAAA,cAAC,OAAI,MAAM,QACT,EAAAA,QAAA,cAACZ,EAAA,CACC,cAAeU,EACf,oBAAqBR,EACrB,qBAAsBC,EACtB,iBAAkBC,EAClB,gBAAe,GACf,WAAYO,EACZ,cAAe,CAAC,EAAAE,OAAS,EACzB,cAAe,CAAC,EAAAC,OAAS,GAExBL,CACH,CACF,EAGJ,CAACM,EAAUC,IAASD,EAAS,UAAYC,EAAK,SAAWD,EAAS,gBAAkBC,EAAK,eAAiBD,EAAS,aAAeC,EAAK,UACzI,EAEAR,EAAc,YAAc,gBAgCrB,SAASpB,EAAkB,CAAE,QAAAqB,EAAS,GAAAQ,EAAI,QAAAC,EAAU,CAAC,CAAE,EAA2B,CACvF,KAAM,CAAE,cAAeX,EAAc,uBAAAY,EAAyB,GAAM,YAAAC,EAAa,WAAYC,EAAmB,CAAC,EAAG,GAAGC,CAAiB,EAAIJ,EAGtIR,KAAgB,WAAQ,IAAMJ,EAAqBC,CAAY,EAAG,CAACA,CAAY,CAAC,EAGhFgB,KAAqB,WAAQ,KAE1B,CACL,MAFqB,4BAAyBD,CAAgB,EAG9D,GAAGD,CACL,GACC,CAACC,EAAkBD,CAAgB,CAAC,EAGjCG,KAAS,WAAQ,IAAM,CAC3B,GAAI,CAACL,GAA0B,CAACC,EAAa,CAE3C,MAAMK,KAAY,gCAA6BhB,CAAO,EACtD,OAAOgB,EAAU,KAAK,EAAI,CAACA,CAAS,EAAI,CAAC,CAC3C,CAEA,SAAO,2BAAwBhB,EAASW,CAAW,CACrD,EAAG,CAACX,EAASU,EAAwBC,CAAW,CAAC,EAEjD,OAAKI,EAAO,OAKRA,EAAO,SAAW,EACb,EAAAZ,QAAA,cAACJ,EAAA,CAAc,QAASgB,EAAO,CAAC,EAAG,cAAed,EAAe,WAAYa,EAAoB,EAKxG,EAAAX,QAAA,cAAC,QAAK,UAAU,SAAS,IAAI,IAAI,MAAM,QACpCY,EAAO,IAAI,CAACE,EAAOC,IAClB,EAAAf,QAAA,cAACJ,EAAA,CAAc,IAAK,GAAGS,CAAE,UAAUU,CAAK,GAAI,QAASD,EAAO,cAAehB,EAAe,WAAYa,EAAoB,CAC3H,CACH,EAdO,IAgBX",
6
- "names": ["streaming_markdown_exports", "__export", "StreamingMarkdown", "__toCommonJS", "import_react", "import_react_markdown", "import_remark_gfm", "import_rehype_raw", "import_harden_react_markdown", "import_kookie_ui", "import_create_markdown_components", "import_markdown_streaming", "hardenReactMarkdown", "hardenReactMarkdownModule", "HardenedMarkdown", "ReactMarkdown", "LINK_PREFIXES", "IMAGE_PREFIXES", "ALLOWED_PROTOCOLS", "DEFAULT_APP_ORIGIN", "resolveDefaultOrigin", "customOrigin", "MarkdownBlock", "content", "defaultOrigin", "components", "React", "remarkGfm", "rehypeRaw", "previous", "next", "id", "options", "enableBlockMemoization", "blockParser", "customComponents", "componentOptions", "markdownComponents", "blocks", "completed", "block", "index"]
4
+ "sourcesContent": ["\"use client\";\n\nimport React, { memo, useMemo, type ReactNode } from \"react\";\nimport ReactMarkdown, { type Components } from \"react-markdown\";\nimport remarkGfm from \"remark-gfm\";\nimport rehypeRaw from \"rehype-raw\";\nimport hardenReactMarkdownModule from \"harden-react-markdown\";\nimport { Box, Flex } from \"@kushagradhawan/kookie-ui\";\nimport { createMarkdownComponents } from \"./create-markdown-components.js\";\nimport { completeUnterminatedMarkdown, parseMarkdownIntoBlocks } from \"./utils/markdown-streaming.js\";\nimport type { MarkdownComponentOptions } from \"./types.js\";\n\n// Handle different export formats\nconst hardenReactMarkdown =\n typeof hardenReactMarkdownModule === \"function\" ? hardenReactMarkdownModule : (hardenReactMarkdownModule as any).default || hardenReactMarkdownModule;\n\nconst HardenedMarkdown = hardenReactMarkdown(ReactMarkdown);\n\nconst LINK_PREFIXES = [\"https://\", \"http://\", \"/\"];\nconst IMAGE_PREFIXES = [\"https://\", \"http://\", \"/\", \"data:\"];\nconst ALLOWED_PROTOCOLS = [\"mailto:\", \"tel:\", \"data:\", \"http:\", \"https:\"];\nconst DEFAULT_APP_ORIGIN = typeof window !== \"undefined\" && window.location?.origin ? window.location.origin : \"https://app.kookie.ai\";\n\n/**\n * Options for StreamingMarkdown component\n */\nexport type StreamingMarkdownOptions = MarkdownComponentOptions & {\n /**\n * Security origin for link/image validation\n * @default window.location.origin or \"https://app.kookie.ai\"\n */\n defaultOrigin?: string;\n\n /**\n * Whether to enable block-level memoization for performance\n * Recommended for streaming scenarios where content updates frequently\n * @default true\n */\n enableBlockMemoization?: boolean;\n\n /**\n * Custom parser for splitting content into blocks\n * If not provided, content will be rendered as a single block\n * For optimal streaming performance, use marked.lexer with GFM enabled\n */\n blockParser?: (content: string) => Array<{ raw?: string }>;\n\n /**\n * Override default component mappings\n */\n components?: Partial<Components>;\n};\n\ntype StreamingMarkdownProps = {\n /**\n * Markdown content to render (supports streaming/incomplete markdown)\n */\n content: string;\n\n /**\n * Unique identifier for this markdown instance (used for keys)\n */\n id: string;\n\n /**\n * Optional configuration\n */\n options?: StreamingMarkdownOptions;\n};\n\ntype MarkdownBlockProps = {\n content: string;\n defaultOrigin: string;\n components: Components;\n};\n\n/**\n * Resolves the default origin for security validation\n */\nfunction resolveDefaultOrigin(customOrigin?: string): string {\n if (customOrigin) {\n return customOrigin;\n }\n return DEFAULT_APP_ORIGIN;\n}\n\n/**\n * Memoized markdown block component for efficient streaming rendering\n */\nconst MarkdownBlock = memo(\n ({ content, defaultOrigin, components }: MarkdownBlockProps) => {\n return (\n <Box width=\"100%\">\n <HardenedMarkdown\n defaultOrigin={defaultOrigin}\n allowedLinkPrefixes={LINK_PREFIXES}\n allowedImagePrefixes={IMAGE_PREFIXES}\n allowedProtocols={ALLOWED_PROTOCOLS}\n allowDataImages\n components={components}\n remarkPlugins={[remarkGfm]}\n rehypePlugins={[rehypeRaw]}\n >\n {content}\n </HardenedMarkdown>\n </Box>\n );\n },\n (previous, next) => previous.content === next.content && previous.defaultOrigin === next.defaultOrigin && previous.components === next.components\n);\n\nMarkdownBlock.displayName = \"MarkdownBlock\";\n\n/**\n * StreamingMarkdown - A drop-in markdown renderer designed for AI streaming.\n *\n * Features:\n * - Unterminated block parsing (handles incomplete markdown during streaming)\n * - Block-level memoization for performance\n * - Security hardening (validates links/images)\n * - GitHub Flavored Markdown support\n * - KookieUI component integration\n * - Code syntax highlighting via CodeBlock\n *\n * @example\n * ```tsx\n * import { StreamingMarkdown } from '@kushagradhawan/kookie-blocks';\n * import { marked } from 'marked';\n *\n * function ChatMessage({ message }) {\n * return (\n * <StreamingMarkdown\n * content={message.content}\n * id={message.id}\n * options={{\n * blockParser: (content) => marked.lexer(content, { gfm: true }),\n * enableBlockMemoization: true,\n * }}\n * />\n * );\n * }\n * ```\n */\nexport function StreamingMarkdown({ content, id, options = {} }: StreamingMarkdownProps) {\n const {\n defaultOrigin: customOrigin,\n enableBlockMemoization = true,\n blockParser,\n components: customComponents,\n // Extract individual options as primitives for stable useMemo deps\n codeBlockCollapsible = false,\n imageComponent,\n inlineCodeHighContrast = true,\n spacing = \"spacious\",\n } = options;\n\n // Resolve security origin\n const defaultOrigin = useMemo(() => resolveDefaultOrigin(customOrigin), [customOrigin]);\n\n // Create component mappings with custom overrides\n // Uses primitive deps to avoid recreating on every render\n const markdownComponents = useMemo(() => {\n const baseComponents = createMarkdownComponents({\n codeBlockCollapsible,\n imageComponent,\n inlineCodeHighContrast,\n spacing,\n });\n return {\n ...baseComponents,\n ...customComponents,\n };\n }, [codeBlockCollapsible, imageComponent, inlineCodeHighContrast, spacing, customComponents]);\n\n // Parse content into blocks for memoization (if enabled and parser provided)\n const blocks = useMemo(() => {\n if (!enableBlockMemoization || !blockParser) {\n // No block splitting - just complete unterminated markdown\n const completed = completeUnterminatedMarkdown(content);\n return completed.trim() ? [completed] : [];\n }\n\n return parseMarkdownIntoBlocks(content, blockParser);\n }, [content, enableBlockMemoization, blockParser]);\n\n if (!blocks.length) {\n return null;\n }\n\n // Single block - no need for wrapper\n if (blocks.length === 1) {\n return <MarkdownBlock content={blocks[0]} defaultOrigin={defaultOrigin} components={markdownComponents} />;\n }\n\n // Multiple blocks - render with flex wrapper\n return (\n <Flex direction=\"column\" gap=\"2\" width=\"100%\">\n {blocks.map((block, index) => (\n <MarkdownBlock key={`${id}-block-${index}`} content={block} defaultOrigin={defaultOrigin} components={markdownComponents} />\n ))}\n </Flex>\n );\n}\n"],
5
+ "mappings": "ukBAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,uBAAAE,IAAA,eAAAC,EAAAH,GAEA,IAAAI,EAAqD,oBACrDC,EAA+C,6BAC/CC,EAAsB,yBACtBC,EAAsB,yBACtBC,EAAsC,oCACtCC,EAA0B,qCAC1BC,EAAyC,2CACzCC,EAAsE,yCAItE,MAAMC,EACJ,OAAO,EAAAC,SAA8B,WAAa,EAAAA,QAA6B,EAAAA,QAAkC,SAAW,EAAAA,QAExHC,EAAmBF,EAAoB,EAAAG,OAAa,EAEpDC,EAAgB,CAAC,WAAY,UAAW,GAAG,EAC3CC,EAAiB,CAAC,WAAY,UAAW,IAAK,OAAO,EACrDC,EAAoB,CAAC,UAAW,OAAQ,QAAS,QAAS,QAAQ,EAClEC,EAAqB,OAAO,OAAW,KAAe,OAAO,UAAU,OAAS,OAAO,SAAS,OAAS,wBA0D/G,SAASC,EAAqBC,EAA+B,CAC3D,OAAIA,GAGGF,CACT,CAKA,MAAMG,KAAgB,QACpB,CAAC,CAAE,QAAAC,EAAS,cAAAC,EAAe,WAAAC,CAAW,IAElC,EAAAC,QAAA,cAAC,OAAI,MAAM,QACT,EAAAA,QAAA,cAACZ,EAAA,CACC,cAAeU,EACf,oBAAqBR,EACrB,qBAAsBC,EACtB,iBAAkBC,EAClB,gBAAe,GACf,WAAYO,EACZ,cAAe,CAAC,EAAAE,OAAS,EACzB,cAAe,CAAC,EAAAC,OAAS,GAExBL,CACH,CACF,EAGJ,CAACM,EAAUC,IAASD,EAAS,UAAYC,EAAK,SAAWD,EAAS,gBAAkBC,EAAK,eAAiBD,EAAS,aAAeC,EAAK,UACzI,EAEAR,EAAc,YAAc,gBAgCrB,SAASpB,EAAkB,CAAE,QAAAqB,EAAS,GAAAQ,EAAI,QAAAC,EAAU,CAAC,CAAE,EAA2B,CACvF,KAAM,CACJ,cAAeX,EACf,uBAAAY,EAAyB,GACzB,YAAAC,EACA,WAAYC,EAEZ,qBAAAC,EAAuB,GACvB,eAAAC,EACA,uBAAAC,EAAyB,GACzB,QAAAC,EAAU,UACZ,EAAIP,EAGER,KAAgB,WAAQ,IAAMJ,EAAqBC,CAAY,EAAG,CAACA,CAAY,CAAC,EAIhFmB,KAAqB,WAAQ,KAO1B,CACL,MAPqB,4BAAyB,CAC9C,qBAAAJ,EACA,eAAAC,EACA,uBAAAC,EACA,QAAAC,CACF,CAAC,EAGC,GAAGJ,CACL,GACC,CAACC,EAAsBC,EAAgBC,EAAwBC,EAASJ,CAAgB,CAAC,EAGtFM,KAAS,WAAQ,IAAM,CAC3B,GAAI,CAACR,GAA0B,CAACC,EAAa,CAE3C,MAAMQ,KAAY,gCAA6BnB,CAAO,EACtD,OAAOmB,EAAU,KAAK,EAAI,CAACA,CAAS,EAAI,CAAC,CAC3C,CAEA,SAAO,2BAAwBnB,EAASW,CAAW,CACrD,EAAG,CAACX,EAASU,EAAwBC,CAAW,CAAC,EAEjD,OAAKO,EAAO,OAKRA,EAAO,SAAW,EACb,EAAAf,QAAA,cAACJ,EAAA,CAAc,QAASmB,EAAO,CAAC,EAAG,cAAejB,EAAe,WAAYgB,EAAoB,EAKxG,EAAAd,QAAA,cAAC,QAAK,UAAU,SAAS,IAAI,IAAI,MAAM,QACpCe,EAAO,IAAI,CAACE,EAAOC,IAClB,EAAAlB,QAAA,cAACJ,EAAA,CAAc,IAAK,GAAGS,CAAE,UAAUa,CAAK,GAAI,QAASD,EAAO,cAAenB,EAAe,WAAYgB,EAAoB,CAC3H,CACH,EAdO,IAgBX",
6
+ "names": ["streaming_markdown_exports", "__export", "StreamingMarkdown", "__toCommonJS", "import_react", "import_react_markdown", "import_remark_gfm", "import_rehype_raw", "import_harden_react_markdown", "import_kookie_ui", "import_create_markdown_components", "import_markdown_streaming", "hardenReactMarkdown", "hardenReactMarkdownModule", "HardenedMarkdown", "ReactMarkdown", "LINK_PREFIXES", "IMAGE_PREFIXES", "ALLOWED_PROTOCOLS", "DEFAULT_APP_ORIGIN", "resolveDefaultOrigin", "customOrigin", "MarkdownBlock", "content", "defaultOrigin", "components", "React", "remarkGfm", "rehypeRaw", "previous", "next", "id", "options", "enableBlockMemoization", "blockParser", "customComponents", "codeBlockCollapsible", "imageComponent", "inlineCodeHighContrast", "spacing", "markdownComponents", "blocks", "completed", "block", "index"]
7
7
  }
@@ -1 +1 @@
1
- {"version":3,"file":"streaming-markdown.d.ts","sourceRoot":"","sources":["../../../../src/components/markdown/streaming-markdown.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAwC,MAAM,OAAO,CAAC;AAC7D,OAAsB,EAAE,KAAK,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAOhE,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAa3D;;GAEG;AACH,MAAM,MAAM,wBAAwB,GAAG,wBAAwB,GAAG;IAChE;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;;;OAIG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAC;IAEjC;;;;OAIG;IACH,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,KAAK,CAAC;QAAE,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAE3D;;OAEG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;CAClC,CAAC;AAEF,KAAK,sBAAsB,GAAG;IAC5B;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAEhB;;OAEG;IACH,EAAE,EAAE,MAAM,CAAC;IAEX;;OAEG;IACH,OAAO,CAAC,EAAE,wBAAwB,CAAC;CACpC,CAAC;AA6CF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,OAAY,EAAE,EAAE,sBAAsB,4BA2CtF"}
1
+ {"version":3,"file":"streaming-markdown.d.ts","sourceRoot":"","sources":["../../../../src/components/markdown/streaming-markdown.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAwC,MAAM,OAAO,CAAC;AAC7D,OAAsB,EAAE,KAAK,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAOhE,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAa3D;;GAEG;AACH,MAAM,MAAM,wBAAwB,GAAG,wBAAwB,GAAG;IAChE;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;;;OAIG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAC;IAEjC;;;;OAIG;IACH,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,KAAK,CAAC;QAAE,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAE3D;;OAEG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;CAClC,CAAC;AAEF,KAAK,sBAAsB,GAAG;IAC5B;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAEhB;;OAEG;IACH,EAAE,EAAE,MAAM,CAAC;IAEX;;OAEG;IACH,OAAO,CAAC,EAAE,wBAAwB,CAAC;CACpC,CAAC;AA6CF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,OAAY,EAAE,EAAE,sBAAsB,4BA2DtF"}
@@ -1,2 +1,2 @@
1
- "use client";import e,{memo as u,useMemo as p}from"react";import M from"react-markdown";import O from"remark-gfm";import P from"rehype-raw";import i from"harden-react-markdown";import{Box as h,Flex as y}from"@kushagradhawan/kookie-ui";import{createMarkdownComponents as C}from"./create-markdown-components.js";import{completeUnterminatedMarkdown as b,parseMarkdownIntoBlocks as B}from"./utils/markdown-streaming.js";const I=typeof i=="function"?i:i.default||i,S=I(M),E=["https://","http://","/"],x=["https://","http://","/","data:"],L=["mailto:","tel:","data:","http:","https:"],A=typeof window<"u"&&window.location?.origin?window.location.origin:"https://app.kookie.ai";function F(o){return o||A}const m=u(({content:o,defaultOrigin:n,components:a})=>e.createElement(h,{width:"100%"},e.createElement(S,{defaultOrigin:n,allowedLinkPrefixes:E,allowedImagePrefixes:x,allowedProtocols:L,allowDataImages:!0,components:a,remarkPlugins:[O],rehypePlugins:[P]},o)),(o,n)=>o.content===n.content&&o.defaultOrigin===n.defaultOrigin&&o.components===n.components);m.displayName="MarkdownBlock";function U({content:o,id:n,options:a={}}){const{defaultOrigin:d,enableBlockMemoization:l=!0,blockParser:s,components:c={},...k}=a,w=p(()=>F(d),[d]),f=p(()=>({...C(k),...c}),[k,c]),r=p(()=>{if(!l||!s){const t=b(o);return t.trim()?[t]:[]}return B(o,s)},[o,l,s]);return r.length?r.length===1?e.createElement(m,{content:r[0],defaultOrigin:w,components:f}):e.createElement(y,{direction:"column",gap:"2",width:"100%"},r.map((t,g)=>e.createElement(m,{key:`${n}-block-${g}`,content:t,defaultOrigin:w,components:f}))):null}export{U as StreamingMarkdown};
1
+ "use client";import e,{memo as P,useMemo as p}from"react";import h from"react-markdown";import y from"remark-gfm";import C from"rehype-raw";import i from"harden-react-markdown";import{Box as b,Flex as B}from"@kushagradhawan/kookie-ui";import{createMarkdownComponents as I}from"./create-markdown-components.js";import{completeUnterminatedMarkdown as S,parseMarkdownIntoBlocks as E}from"./utils/markdown-streaming.js";const x=typeof i=="function"?i:i.default||i,L=x(h),A=["https://","http://","/"],F=["https://","http://","/","data:"],R=["mailto:","tel:","data:","http:","https:"],_=typeof window<"u"&&window.location?.origin?window.location.origin:"https://app.kookie.ai";function D(o){return o||_}const m=P(({content:o,defaultOrigin:n,components:a})=>e.createElement(b,{width:"100%"},e.createElement(L,{defaultOrigin:n,allowedLinkPrefixes:A,allowedImagePrefixes:F,allowedProtocols:R,allowDataImages:!0,components:a,remarkPlugins:[y],rehypePlugins:[C]},o)),(o,n)=>o.content===n.content&&o.defaultOrigin===n.defaultOrigin&&o.components===n.components);m.displayName="MarkdownBlock";function j({content:o,id:n,options:a={}}){const{defaultOrigin:l,enableBlockMemoization:d=!0,blockParser:s,components:c,codeBlockCollapsible:k=!1,imageComponent:g,inlineCodeHighContrast:f=!0,spacing:w="spacious"}=a,u=p(()=>D(l),[l]),M=p(()=>({...I({codeBlockCollapsible:k,imageComponent:g,inlineCodeHighContrast:f,spacing:w}),...c}),[k,g,f,w,c]),r=p(()=>{if(!d||!s){const t=S(o);return t.trim()?[t]:[]}return E(o,s)},[o,d,s]);return r.length?r.length===1?e.createElement(m,{content:r[0],defaultOrigin:u,components:M}):e.createElement(B,{direction:"column",gap:"2",width:"100%"},r.map((t,O)=>e.createElement(m,{key:`${n}-block-${O}`,content:t,defaultOrigin:u,components:M}))):null}export{j as StreamingMarkdown};
2
2
  //# sourceMappingURL=streaming-markdown.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/components/markdown/streaming-markdown.tsx"],
4
- "sourcesContent": ["\"use client\";\n\nimport React, { memo, useMemo, type ReactNode } from \"react\";\nimport ReactMarkdown, { type Components } from \"react-markdown\";\nimport remarkGfm from \"remark-gfm\";\nimport rehypeRaw from \"rehype-raw\";\nimport hardenReactMarkdownModule from \"harden-react-markdown\";\nimport { Box, Flex } from \"@kushagradhawan/kookie-ui\";\nimport { createMarkdownComponents } from \"./create-markdown-components.js\";\nimport { completeUnterminatedMarkdown, parseMarkdownIntoBlocks } from \"./utils/markdown-streaming.js\";\nimport type { MarkdownComponentOptions } from \"./types.js\";\n\n// Handle different export formats\nconst hardenReactMarkdown =\n typeof hardenReactMarkdownModule === \"function\" ? hardenReactMarkdownModule : (hardenReactMarkdownModule as any).default || hardenReactMarkdownModule;\n\nconst HardenedMarkdown = hardenReactMarkdown(ReactMarkdown);\n\nconst LINK_PREFIXES = [\"https://\", \"http://\", \"/\"];\nconst IMAGE_PREFIXES = [\"https://\", \"http://\", \"/\", \"data:\"];\nconst ALLOWED_PROTOCOLS = [\"mailto:\", \"tel:\", \"data:\", \"http:\", \"https:\"];\nconst DEFAULT_APP_ORIGIN = typeof window !== \"undefined\" && window.location?.origin ? window.location.origin : \"https://app.kookie.ai\";\n\n/**\n * Options for StreamingMarkdown component\n */\nexport type StreamingMarkdownOptions = MarkdownComponentOptions & {\n /**\n * Security origin for link/image validation\n * @default window.location.origin or \"https://app.kookie.ai\"\n */\n defaultOrigin?: string;\n\n /**\n * Whether to enable block-level memoization for performance\n * Recommended for streaming scenarios where content updates frequently\n * @default true\n */\n enableBlockMemoization?: boolean;\n\n /**\n * Custom parser for splitting content into blocks\n * If not provided, content will be rendered as a single block\n * For optimal streaming performance, use marked.lexer with GFM enabled\n */\n blockParser?: (content: string) => Array<{ raw?: string }>;\n\n /**\n * Override default component mappings\n */\n components?: Partial<Components>;\n};\n\ntype StreamingMarkdownProps = {\n /**\n * Markdown content to render (supports streaming/incomplete markdown)\n */\n content: string;\n\n /**\n * Unique identifier for this markdown instance (used for keys)\n */\n id: string;\n\n /**\n * Optional configuration\n */\n options?: StreamingMarkdownOptions;\n};\n\ntype MarkdownBlockProps = {\n content: string;\n defaultOrigin: string;\n components: Components;\n};\n\n/**\n * Resolves the default origin for security validation\n */\nfunction resolveDefaultOrigin(customOrigin?: string): string {\n if (customOrigin) {\n return customOrigin;\n }\n return DEFAULT_APP_ORIGIN;\n}\n\n/**\n * Memoized markdown block component for efficient streaming rendering\n */\nconst MarkdownBlock = memo(\n ({ content, defaultOrigin, components }: MarkdownBlockProps) => {\n return (\n <Box width=\"100%\">\n <HardenedMarkdown\n defaultOrigin={defaultOrigin}\n allowedLinkPrefixes={LINK_PREFIXES}\n allowedImagePrefixes={IMAGE_PREFIXES}\n allowedProtocols={ALLOWED_PROTOCOLS}\n allowDataImages\n components={components}\n remarkPlugins={[remarkGfm]}\n rehypePlugins={[rehypeRaw]}\n >\n {content}\n </HardenedMarkdown>\n </Box>\n );\n },\n (previous, next) => previous.content === next.content && previous.defaultOrigin === next.defaultOrigin && previous.components === next.components\n);\n\nMarkdownBlock.displayName = \"MarkdownBlock\";\n\n/**\n * StreamingMarkdown - A drop-in markdown renderer designed for AI streaming.\n *\n * Features:\n * - Unterminated block parsing (handles incomplete markdown during streaming)\n * - Block-level memoization for performance\n * - Security hardening (validates links/images)\n * - GitHub Flavored Markdown support\n * - KookieUI component integration\n * - Code syntax highlighting via CodeBlock\n *\n * @example\n * ```tsx\n * import { StreamingMarkdown } from '@kushagradhawan/kookie-blocks';\n * import { marked } from 'marked';\n *\n * function ChatMessage({ message }) {\n * return (\n * <StreamingMarkdown\n * content={message.content}\n * id={message.id}\n * options={{\n * blockParser: (content) => marked.lexer(content, { gfm: true }),\n * enableBlockMemoization: true,\n * }}\n * />\n * );\n * }\n * ```\n */\nexport function StreamingMarkdown({ content, id, options = {} }: StreamingMarkdownProps) {\n const { defaultOrigin: customOrigin, enableBlockMemoization = true, blockParser, components: customComponents = {}, ...componentOptions } = options;\n\n // Resolve security origin\n const defaultOrigin = useMemo(() => resolveDefaultOrigin(customOrigin), [customOrigin]);\n\n // Create component mappings with custom overrides\n const markdownComponents = useMemo(() => {\n const baseComponents = createMarkdownComponents(componentOptions);\n return {\n ...baseComponents,\n ...customComponents,\n };\n }, [componentOptions, customComponents]);\n\n // Parse content into blocks for memoization (if enabled and parser provided)\n const blocks = useMemo(() => {\n if (!enableBlockMemoization || !blockParser) {\n // No block splitting - just complete unterminated markdown\n const completed = completeUnterminatedMarkdown(content);\n return completed.trim() ? [completed] : [];\n }\n\n return parseMarkdownIntoBlocks(content, blockParser);\n }, [content, enableBlockMemoization, blockParser]);\n\n if (!blocks.length) {\n return null;\n }\n\n // Single block - no need for wrapper\n if (blocks.length === 1) {\n return <MarkdownBlock content={blocks[0]} defaultOrigin={defaultOrigin} components={markdownComponents} />;\n }\n\n // Multiple blocks - render with flex wrapper\n return (\n <Flex direction=\"column\" gap=\"2\" width=\"100%\">\n {blocks.map((block, index) => (\n <MarkdownBlock key={`${id}-block-${index}`} content={block} defaultOrigin={defaultOrigin} components={markdownComponents} />\n ))}\n </Flex>\n );\n}\n"],
5
- "mappings": "aAEA,OAAOA,GAAS,QAAAC,EAAM,WAAAC,MAA+B,QACrD,OAAOC,MAAwC,iBAC/C,OAAOC,MAAe,aACtB,OAAOC,MAAe,aACtB,OAAOC,MAA+B,wBACtC,OAAS,OAAAC,EAAK,QAAAC,MAAY,4BAC1B,OAAS,4BAAAC,MAAgC,kCACzC,OAAS,gCAAAC,EAA8B,2BAAAC,MAA+B,gCAItE,MAAMC,EACJ,OAAON,GAA8B,WAAaA,EAA6BA,EAAkC,SAAWA,EAExHO,EAAmBD,EAAoBT,CAAa,EAEpDW,EAAgB,CAAC,WAAY,UAAW,GAAG,EAC3CC,EAAiB,CAAC,WAAY,UAAW,IAAK,OAAO,EACrDC,EAAoB,CAAC,UAAW,OAAQ,QAAS,QAAS,QAAQ,EAClEC,EAAqB,OAAO,OAAW,KAAe,OAAO,UAAU,OAAS,OAAO,SAAS,OAAS,wBA0D/G,SAASC,EAAqBC,EAA+B,CAC3D,OAAIA,GAGGF,CACT,CAKA,MAAMG,EAAgBnB,EACpB,CAAC,CAAE,QAAAoB,EAAS,cAAAC,EAAe,WAAAC,CAAW,IAElCvB,EAAA,cAACO,EAAA,CAAI,MAAM,QACTP,EAAA,cAACa,EAAA,CACC,cAAeS,EACf,oBAAqBR,EACrB,qBAAsBC,EACtB,iBAAkBC,EAClB,gBAAe,GACf,WAAYO,EACZ,cAAe,CAACnB,CAAS,EACzB,cAAe,CAACC,CAAS,GAExBgB,CACH,CACF,EAGJ,CAACG,EAAUC,IAASD,EAAS,UAAYC,EAAK,SAAWD,EAAS,gBAAkBC,EAAK,eAAiBD,EAAS,aAAeC,EAAK,UACzI,EAEAL,EAAc,YAAc,gBAgCrB,SAASM,EAAkB,CAAE,QAAAL,EAAS,GAAAM,EAAI,QAAAC,EAAU,CAAC,CAAE,EAA2B,CACvF,KAAM,CAAE,cAAeT,EAAc,uBAAAU,EAAyB,GAAM,YAAAC,EAAa,WAAYC,EAAmB,CAAC,EAAG,GAAGC,CAAiB,EAAIJ,EAGtIN,EAAgBpB,EAAQ,IAAMgB,EAAqBC,CAAY,EAAG,CAACA,CAAY,CAAC,EAGhFc,EAAqB/B,EAAQ,KAE1B,CACL,GAFqBO,EAAyBuB,CAAgB,EAG9D,GAAGD,CACL,GACC,CAACC,EAAkBD,CAAgB,CAAC,EAGjCG,EAAShC,EAAQ,IAAM,CAC3B,GAAI,CAAC2B,GAA0B,CAACC,EAAa,CAE3C,MAAMK,EAAYzB,EAA6BW,CAAO,EACtD,OAAOc,EAAU,KAAK,EAAI,CAACA,CAAS,EAAI,CAAC,CAC3C,CAEA,OAAOxB,EAAwBU,EAASS,CAAW,CACrD,EAAG,CAACT,EAASQ,EAAwBC,CAAW,CAAC,EAEjD,OAAKI,EAAO,OAKRA,EAAO,SAAW,EACblC,EAAA,cAACoB,EAAA,CAAc,QAASc,EAAO,CAAC,EAAG,cAAeZ,EAAe,WAAYW,EAAoB,EAKxGjC,EAAA,cAACQ,EAAA,CAAK,UAAU,SAAS,IAAI,IAAI,MAAM,QACpC0B,EAAO,IAAI,CAACE,EAAOC,IAClBrC,EAAA,cAACoB,EAAA,CAAc,IAAK,GAAGO,CAAE,UAAUU,CAAK,GAAI,QAASD,EAAO,cAAed,EAAe,WAAYW,EAAoB,CAC3H,CACH,EAdO,IAgBX",
6
- "names": ["React", "memo", "useMemo", "ReactMarkdown", "remarkGfm", "rehypeRaw", "hardenReactMarkdownModule", "Box", "Flex", "createMarkdownComponents", "completeUnterminatedMarkdown", "parseMarkdownIntoBlocks", "hardenReactMarkdown", "HardenedMarkdown", "LINK_PREFIXES", "IMAGE_PREFIXES", "ALLOWED_PROTOCOLS", "DEFAULT_APP_ORIGIN", "resolveDefaultOrigin", "customOrigin", "MarkdownBlock", "content", "defaultOrigin", "components", "previous", "next", "StreamingMarkdown", "id", "options", "enableBlockMemoization", "blockParser", "customComponents", "componentOptions", "markdownComponents", "blocks", "completed", "block", "index"]
4
+ "sourcesContent": ["\"use client\";\n\nimport React, { memo, useMemo, type ReactNode } from \"react\";\nimport ReactMarkdown, { type Components } from \"react-markdown\";\nimport remarkGfm from \"remark-gfm\";\nimport rehypeRaw from \"rehype-raw\";\nimport hardenReactMarkdownModule from \"harden-react-markdown\";\nimport { Box, Flex } from \"@kushagradhawan/kookie-ui\";\nimport { createMarkdownComponents } from \"./create-markdown-components.js\";\nimport { completeUnterminatedMarkdown, parseMarkdownIntoBlocks } from \"./utils/markdown-streaming.js\";\nimport type { MarkdownComponentOptions } from \"./types.js\";\n\n// Handle different export formats\nconst hardenReactMarkdown =\n typeof hardenReactMarkdownModule === \"function\" ? hardenReactMarkdownModule : (hardenReactMarkdownModule as any).default || hardenReactMarkdownModule;\n\nconst HardenedMarkdown = hardenReactMarkdown(ReactMarkdown);\n\nconst LINK_PREFIXES = [\"https://\", \"http://\", \"/\"];\nconst IMAGE_PREFIXES = [\"https://\", \"http://\", \"/\", \"data:\"];\nconst ALLOWED_PROTOCOLS = [\"mailto:\", \"tel:\", \"data:\", \"http:\", \"https:\"];\nconst DEFAULT_APP_ORIGIN = typeof window !== \"undefined\" && window.location?.origin ? window.location.origin : \"https://app.kookie.ai\";\n\n/**\n * Options for StreamingMarkdown component\n */\nexport type StreamingMarkdownOptions = MarkdownComponentOptions & {\n /**\n * Security origin for link/image validation\n * @default window.location.origin or \"https://app.kookie.ai\"\n */\n defaultOrigin?: string;\n\n /**\n * Whether to enable block-level memoization for performance\n * Recommended for streaming scenarios where content updates frequently\n * @default true\n */\n enableBlockMemoization?: boolean;\n\n /**\n * Custom parser for splitting content into blocks\n * If not provided, content will be rendered as a single block\n * For optimal streaming performance, use marked.lexer with GFM enabled\n */\n blockParser?: (content: string) => Array<{ raw?: string }>;\n\n /**\n * Override default component mappings\n */\n components?: Partial<Components>;\n};\n\ntype StreamingMarkdownProps = {\n /**\n * Markdown content to render (supports streaming/incomplete markdown)\n */\n content: string;\n\n /**\n * Unique identifier for this markdown instance (used for keys)\n */\n id: string;\n\n /**\n * Optional configuration\n */\n options?: StreamingMarkdownOptions;\n};\n\ntype MarkdownBlockProps = {\n content: string;\n defaultOrigin: string;\n components: Components;\n};\n\n/**\n * Resolves the default origin for security validation\n */\nfunction resolveDefaultOrigin(customOrigin?: string): string {\n if (customOrigin) {\n return customOrigin;\n }\n return DEFAULT_APP_ORIGIN;\n}\n\n/**\n * Memoized markdown block component for efficient streaming rendering\n */\nconst MarkdownBlock = memo(\n ({ content, defaultOrigin, components }: MarkdownBlockProps) => {\n return (\n <Box width=\"100%\">\n <HardenedMarkdown\n defaultOrigin={defaultOrigin}\n allowedLinkPrefixes={LINK_PREFIXES}\n allowedImagePrefixes={IMAGE_PREFIXES}\n allowedProtocols={ALLOWED_PROTOCOLS}\n allowDataImages\n components={components}\n remarkPlugins={[remarkGfm]}\n rehypePlugins={[rehypeRaw]}\n >\n {content}\n </HardenedMarkdown>\n </Box>\n );\n },\n (previous, next) => previous.content === next.content && previous.defaultOrigin === next.defaultOrigin && previous.components === next.components\n);\n\nMarkdownBlock.displayName = \"MarkdownBlock\";\n\n/**\n * StreamingMarkdown - A drop-in markdown renderer designed for AI streaming.\n *\n * Features:\n * - Unterminated block parsing (handles incomplete markdown during streaming)\n * - Block-level memoization for performance\n * - Security hardening (validates links/images)\n * - GitHub Flavored Markdown support\n * - KookieUI component integration\n * - Code syntax highlighting via CodeBlock\n *\n * @example\n * ```tsx\n * import { StreamingMarkdown } from '@kushagradhawan/kookie-blocks';\n * import { marked } from 'marked';\n *\n * function ChatMessage({ message }) {\n * return (\n * <StreamingMarkdown\n * content={message.content}\n * id={message.id}\n * options={{\n * blockParser: (content) => marked.lexer(content, { gfm: true }),\n * enableBlockMemoization: true,\n * }}\n * />\n * );\n * }\n * ```\n */\nexport function StreamingMarkdown({ content, id, options = {} }: StreamingMarkdownProps) {\n const {\n defaultOrigin: customOrigin,\n enableBlockMemoization = true,\n blockParser,\n components: customComponents,\n // Extract individual options as primitives for stable useMemo deps\n codeBlockCollapsible = false,\n imageComponent,\n inlineCodeHighContrast = true,\n spacing = \"spacious\",\n } = options;\n\n // Resolve security origin\n const defaultOrigin = useMemo(() => resolveDefaultOrigin(customOrigin), [customOrigin]);\n\n // Create component mappings with custom overrides\n // Uses primitive deps to avoid recreating on every render\n const markdownComponents = useMemo(() => {\n const baseComponents = createMarkdownComponents({\n codeBlockCollapsible,\n imageComponent,\n inlineCodeHighContrast,\n spacing,\n });\n return {\n ...baseComponents,\n ...customComponents,\n };\n }, [codeBlockCollapsible, imageComponent, inlineCodeHighContrast, spacing, customComponents]);\n\n // Parse content into blocks for memoization (if enabled and parser provided)\n const blocks = useMemo(() => {\n if (!enableBlockMemoization || !blockParser) {\n // No block splitting - just complete unterminated markdown\n const completed = completeUnterminatedMarkdown(content);\n return completed.trim() ? [completed] : [];\n }\n\n return parseMarkdownIntoBlocks(content, blockParser);\n }, [content, enableBlockMemoization, blockParser]);\n\n if (!blocks.length) {\n return null;\n }\n\n // Single block - no need for wrapper\n if (blocks.length === 1) {\n return <MarkdownBlock content={blocks[0]} defaultOrigin={defaultOrigin} components={markdownComponents} />;\n }\n\n // Multiple blocks - render with flex wrapper\n return (\n <Flex direction=\"column\" gap=\"2\" width=\"100%\">\n {blocks.map((block, index) => (\n <MarkdownBlock key={`${id}-block-${index}`} content={block} defaultOrigin={defaultOrigin} components={markdownComponents} />\n ))}\n </Flex>\n );\n}\n"],
5
+ "mappings": "aAEA,OAAOA,GAAS,QAAAC,EAAM,WAAAC,MAA+B,QACrD,OAAOC,MAAwC,iBAC/C,OAAOC,MAAe,aACtB,OAAOC,MAAe,aACtB,OAAOC,MAA+B,wBACtC,OAAS,OAAAC,EAAK,QAAAC,MAAY,4BAC1B,OAAS,4BAAAC,MAAgC,kCACzC,OAAS,gCAAAC,EAA8B,2BAAAC,MAA+B,gCAItE,MAAMC,EACJ,OAAON,GAA8B,WAAaA,EAA6BA,EAAkC,SAAWA,EAExHO,EAAmBD,EAAoBT,CAAa,EAEpDW,EAAgB,CAAC,WAAY,UAAW,GAAG,EAC3CC,EAAiB,CAAC,WAAY,UAAW,IAAK,OAAO,EACrDC,EAAoB,CAAC,UAAW,OAAQ,QAAS,QAAS,QAAQ,EAClEC,EAAqB,OAAO,OAAW,KAAe,OAAO,UAAU,OAAS,OAAO,SAAS,OAAS,wBA0D/G,SAASC,EAAqBC,EAA+B,CAC3D,OAAIA,GAGGF,CACT,CAKA,MAAMG,EAAgBnB,EACpB,CAAC,CAAE,QAAAoB,EAAS,cAAAC,EAAe,WAAAC,CAAW,IAElCvB,EAAA,cAACO,EAAA,CAAI,MAAM,QACTP,EAAA,cAACa,EAAA,CACC,cAAeS,EACf,oBAAqBR,EACrB,qBAAsBC,EACtB,iBAAkBC,EAClB,gBAAe,GACf,WAAYO,EACZ,cAAe,CAACnB,CAAS,EACzB,cAAe,CAACC,CAAS,GAExBgB,CACH,CACF,EAGJ,CAACG,EAAUC,IAASD,EAAS,UAAYC,EAAK,SAAWD,EAAS,gBAAkBC,EAAK,eAAiBD,EAAS,aAAeC,EAAK,UACzI,EAEAL,EAAc,YAAc,gBAgCrB,SAASM,EAAkB,CAAE,QAAAL,EAAS,GAAAM,EAAI,QAAAC,EAAU,CAAC,CAAE,EAA2B,CACvF,KAAM,CACJ,cAAeT,EACf,uBAAAU,EAAyB,GACzB,YAAAC,EACA,WAAYC,EAEZ,qBAAAC,EAAuB,GACvB,eAAAC,EACA,uBAAAC,EAAyB,GACzB,QAAAC,EAAU,UACZ,EAAIP,EAGEN,EAAgBpB,EAAQ,IAAMgB,EAAqBC,CAAY,EAAG,CAACA,CAAY,CAAC,EAIhFiB,EAAqBlC,EAAQ,KAO1B,CACL,GAPqBO,EAAyB,CAC9C,qBAAAuB,EACA,eAAAC,EACA,uBAAAC,EACA,QAAAC,CACF,CAAC,EAGC,GAAGJ,CACL,GACC,CAACC,EAAsBC,EAAgBC,EAAwBC,EAASJ,CAAgB,CAAC,EAGtFM,EAASnC,EAAQ,IAAM,CAC3B,GAAI,CAAC2B,GAA0B,CAACC,EAAa,CAE3C,MAAMQ,EAAY5B,EAA6BW,CAAO,EACtD,OAAOiB,EAAU,KAAK,EAAI,CAACA,CAAS,EAAI,CAAC,CAC3C,CAEA,OAAO3B,EAAwBU,EAASS,CAAW,CACrD,EAAG,CAACT,EAASQ,EAAwBC,CAAW,CAAC,EAEjD,OAAKO,EAAO,OAKRA,EAAO,SAAW,EACbrC,EAAA,cAACoB,EAAA,CAAc,QAASiB,EAAO,CAAC,EAAG,cAAef,EAAe,WAAYc,EAAoB,EAKxGpC,EAAA,cAACQ,EAAA,CAAK,UAAU,SAAS,IAAI,IAAI,MAAM,QACpC6B,EAAO,IAAI,CAACE,EAAOC,IAClBxC,EAAA,cAACoB,EAAA,CAAc,IAAK,GAAGO,CAAE,UAAUa,CAAK,GAAI,QAASD,EAAO,cAAejB,EAAe,WAAYc,EAAoB,CAC3H,CACH,EAdO,IAgBX",
6
+ "names": ["React", "memo", "useMemo", "ReactMarkdown", "remarkGfm", "rehypeRaw", "hardenReactMarkdownModule", "Box", "Flex", "createMarkdownComponents", "completeUnterminatedMarkdown", "parseMarkdownIntoBlocks", "hardenReactMarkdown", "HardenedMarkdown", "LINK_PREFIXES", "IMAGE_PREFIXES", "ALLOWED_PROTOCOLS", "DEFAULT_APP_ORIGIN", "resolveDefaultOrigin", "customOrigin", "MarkdownBlock", "content", "defaultOrigin", "components", "previous", "next", "StreamingMarkdown", "id", "options", "enableBlockMemoization", "blockParser", "customComponents", "codeBlockCollapsible", "imageComponent", "inlineCodeHighContrast", "spacing", "markdownComponents", "blocks", "completed", "block", "index"]
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kushagradhawan/kookie-blocks",
3
- "version": "0.1.39",
3
+ "version": "0.1.40",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/KushagraDhawan1997/kookie-blocks.git"
@@ -102,7 +102,7 @@
102
102
  "shiki": "^1.24.2"
103
103
  },
104
104
  "peerDependencies": {
105
- "@kushagradhawan/kookie-ui": ">=0.1.107",
105
+ "@kushagradhawan/kookie-ui": ">=0.1.111",
106
106
  "react": "^18.0.0 || ^19.0.0",
107
107
  "react-dom": "^18.0.0 || ^19.0.0"
108
108
  },
@@ -142,19 +142,35 @@ MarkdownBlock.displayName = "MarkdownBlock";
142
142
  * ```
143
143
  */
144
144
  export function StreamingMarkdown({ content, id, options = {} }: StreamingMarkdownProps) {
145
- const { defaultOrigin: customOrigin, enableBlockMemoization = true, blockParser, components: customComponents = {}, ...componentOptions } = options;
145
+ const {
146
+ defaultOrigin: customOrigin,
147
+ enableBlockMemoization = true,
148
+ blockParser,
149
+ components: customComponents,
150
+ // Extract individual options as primitives for stable useMemo deps
151
+ codeBlockCollapsible = false,
152
+ imageComponent,
153
+ inlineCodeHighContrast = true,
154
+ spacing = "spacious",
155
+ } = options;
146
156
 
147
157
  // Resolve security origin
148
158
  const defaultOrigin = useMemo(() => resolveDefaultOrigin(customOrigin), [customOrigin]);
149
159
 
150
160
  // Create component mappings with custom overrides
161
+ // Uses primitive deps to avoid recreating on every render
151
162
  const markdownComponents = useMemo(() => {
152
- const baseComponents = createMarkdownComponents(componentOptions);
163
+ const baseComponents = createMarkdownComponents({
164
+ codeBlockCollapsible,
165
+ imageComponent,
166
+ inlineCodeHighContrast,
167
+ spacing,
168
+ });
153
169
  return {
154
170
  ...baseComponents,
155
171
  ...customComponents,
156
172
  };
157
- }, [componentOptions, customComponents]);
173
+ }, [codeBlockCollapsible, imageComponent, inlineCodeHighContrast, spacing, customComponents]);
158
174
 
159
175
  // Parse content into blocks for memoization (if enabled and parser provided)
160
176
  const blocks = useMemo(() => {