@changerawr/markdown 1.1.11 → 1.2.0

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.
@@ -22,6 +22,37 @@ interface Extension {
22
22
  parseRules: ParseRule[];
23
23
  renderRules: RenderRule[];
24
24
  }
25
+ /**
26
+ * Props passed to a framework component registered on a ComponentRenderRule.
27
+ * The `children` type is intentionally `unknown` here — each framework binding
28
+ * (React, Astro, etc.) narrows it to the correct type in its own module.
29
+ */
30
+ interface ComponentTokenProps {
31
+ token: MarkdownToken;
32
+ /** Pre-rendered children (framework-specific ReactNode, Astro.slots, etc.) */
33
+ children?: unknown;
34
+ }
35
+ /**
36
+ * A render rule that optionally carries a framework component.
37
+ * The `render` function is always required as a string-output fallback used by
38
+ * HTML / Tailwind / JSON / Astro outputs. Framework renderers (React, etc.)
39
+ * will prefer `component` when present.
40
+ */
41
+ interface ComponentRenderRule extends RenderRule {
42
+ /**
43
+ * Framework component (React.ComponentType, Svelte component, etc.).
44
+ * Typed as `unknown` here; each framework module provides a narrowed variant.
45
+ */
46
+ component?: unknown;
47
+ }
48
+ /**
49
+ * An Extension that may carry framework components on its render rules.
50
+ * It is fully compatible with the base `Extension` type and can be registered
51
+ * with the engine like any regular extension — the engine only uses `render`.
52
+ */
53
+ interface ComponentExtension extends Extension {
54
+ renderRules: ComponentRenderRule[];
55
+ }
25
56
  type OutputFormat = 'html' | 'tailwind' | 'json';
26
57
  interface RendererConfig {
27
58
  format: OutputFormat;
@@ -61,6 +92,33 @@ interface ExtensionRegistration {
61
92
  conflictingRules?: string[];
62
93
  }
63
94
 
95
+ /**
96
+ * Props passed to a React component registered on a ReactComponentRenderRule.
97
+ */
98
+ interface ReactComponentTokenProps {
99
+ token: MarkdownToken;
100
+ /** Rendered children as React nodes (nil when the token has no children) */
101
+ children?: React.ReactNode;
102
+ }
103
+ /**
104
+ * A render rule that optionally carries a React component.
105
+ */
106
+ interface ReactComponentRenderRule {
107
+ type: string;
108
+ /** React component used by the React renderer when present. */
109
+ component?: React.ComponentType<ReactComponentTokenProps>;
110
+ /** String fallback — used by HTML / Tailwind / Astro renderers. */
111
+ render: (token: MarkdownToken) => string;
112
+ }
113
+ /**
114
+ * A ComponentExtension with React components attached to its render rules.
115
+ * Compatible with the base Extension interface — pass directly to createEngine().
116
+ */
117
+ interface ReactComponentExtension {
118
+ name: string;
119
+ parseRules: ParseRule[];
120
+ renderRules: ReactComponentRenderRule[];
121
+ }
64
122
  /**
65
123
  * Props for the MarkdownRenderer component
66
124
  */
@@ -89,6 +147,12 @@ interface MarkdownRendererProps {
89
147
  onError?: (error: Error) => void;
90
148
  /** Custom extensions to register */
91
149
  extensions?: Extension[];
150
+ /**
151
+ * Component extensions — extensions whose render rules carry React components.
152
+ * When provided the renderer switches from dangerouslySetInnerHTML to a React
153
+ * element tree, giving component-rendered tokens full React lifecycle support.
154
+ */
155
+ componentExtensions?: ReactComponentExtension[];
92
156
  /** Whether to sanitize HTML output */
93
157
  sanitize?: boolean;
94
158
  /** Allow unsafe HTML (use with caution) */
@@ -106,6 +170,8 @@ interface UseMarkdownOptions {
106
170
  debug?: boolean;
107
171
  /** Custom extensions */
108
172
  extensions?: Extension[];
173
+ /** Component extensions (React-component-bearing extensions) */
174
+ componentExtensions?: ReactComponentExtension[];
109
175
  /** Debounce delay in milliseconds */
110
176
  debounceMs?: number;
111
177
  /** Whether to memoize results */
@@ -129,6 +195,11 @@ interface UseMarkdownResult {
129
195
  render: (content: string) => void;
130
196
  /** Clear current state */
131
197
  clear: () => void;
198
+ /**
199
+ * Render an arbitrary batch of tokens to an HTML string using the current engine.
200
+ * Used internally by TokenTreeRenderer to render non-component token groups.
201
+ */
202
+ renderBatch: (tokens: MarkdownToken[]) => string;
132
203
  }
133
204
  /**
134
205
  * Options for useMarkdownEngine hook
@@ -168,11 +239,37 @@ interface MarkdownDebugInfo {
168
239
  * />
169
240
  * ```
170
241
  */
171
- declare function MarkdownRenderer({ content, className, config, format, as: Component, wrapperProps, debug, errorFallback, loading, onRender, onError, extensions, sanitize, allowUnsafeHtml, ...restProps }: MarkdownRendererProps): react_jsx_runtime.JSX.Element;
242
+ declare function MarkdownRenderer({ content, className, config, format, as: Component, wrapperProps, debug, errorFallback, loading, onRender, onError, extensions, componentExtensions, sanitize, allowUnsafeHtml, ...restProps }: MarkdownRendererProps): react_jsx_runtime.JSX.Element;
172
243
  declare namespace MarkdownRenderer {
173
244
  var displayName: string;
174
245
  }
175
246
 
247
+ interface TokenTreeRendererProps {
248
+ tokens: MarkdownToken[];
249
+ /**
250
+ * Map from token type → the rule that carries a React component.
251
+ * Only rules that actually have a `component` field should be in this map.
252
+ */
253
+ componentMap: Map<string, ReactComponentRenderRule>;
254
+ /**
255
+ * Renders a batch of tokens to an HTML string using the engine.
256
+ * Used for all tokens that don't have a React component attached.
257
+ */
258
+ renderBatch: (tokens: MarkdownToken[]) => string;
259
+ }
260
+ /**
261
+ * TokenTreeRenderer — walks a token array and renders each token as either:
262
+ * • A React component (when the token type has a `component` registered)
263
+ * • A raw HTML chunk via `dangerouslySetInnerHTML` (everything else)
264
+ *
265
+ * Consecutive non-component tokens are batched into a single HTML chunk so that
266
+ * list-grouping and other renderer logic (which operates on slices) still works.
267
+ *
268
+ * ⚠ Limitation: list items must not be interleaved with component tokens —
269
+ * the list-grouping in MarkdownRenderer operates on contiguous slices.
270
+ */
271
+ declare function TokenTreeRenderer({ tokens, componentMap, renderBatch }: TokenTreeRendererProps): React.ReactElement;
272
+
176
273
  interface CacheStats {
177
274
  size: number;
178
275
  capacity: number;
@@ -280,5 +377,39 @@ declare function useMarkdownDebug(content: string): {
280
377
  contentLength: number;
281
378
  };
282
379
  };
380
+ /**
381
+ * Hook for rendering markdown with React component extensions (TipTap-style).
382
+ *
383
+ * Returns the same values as `useMarkdown` plus:
384
+ * - `componentMap` — Map<tokenType, ReactComponentRenderRule> for TokenTreeRenderer
385
+ *
386
+ * @example
387
+ * ```tsx
388
+ * const CardExtension: ReactComponentExtension = {
389
+ * name: 'card',
390
+ * parseRules: [{ name: 'card', pattern: /:::card\n([\s\S]*?)\n:::/, render: (m) => ({ type: 'card', content: m[1] ?? '', raw: m[0] ?? '' }) }],
391
+ * renderRules: [{
392
+ * type: 'card',
393
+ * component: ({ token, children }) => <div className="card">{children}</div>,
394
+ * render: (token) => `<div class="card">${token.content}</div>`
395
+ * }]
396
+ * };
397
+ *
398
+ * const { tokens, componentMap, renderBatch } = useMarkdownComponents(content, { componentExtensions: [CardExtension] });
399
+ * ```
400
+ */
401
+ declare function useMarkdownComponents(content: string, options?: UseMarkdownOptions & {
402
+ componentExtensions?: ReactComponentExtension[];
403
+ }): {
404
+ componentMap: Map<string, ReactComponentRenderRule>;
405
+ html: string;
406
+ tokens: MarkdownToken[];
407
+ isLoading: boolean;
408
+ error: Error | null;
409
+ debug: MarkdownDebugInfo | null;
410
+ render: (content: string) => void;
411
+ clear: () => void;
412
+ renderBatch: (tokens: MarkdownToken[]) => string;
413
+ };
283
414
 
284
- export { type EngineConfig, type Extension, type MarkdownDebugInfo, type MarkdownEngineHookOptions, MarkdownRenderer, type MarkdownRendererProps, type MarkdownToken, type OutputFormat, type UseMarkdownOptions, type UseMarkdownResult, useMarkdown, useMarkdownDebug, useMarkdownEngine };
415
+ export { type ComponentExtension, type ComponentRenderRule, type ComponentTokenProps, type EngineConfig, type Extension, type MarkdownDebugInfo, type MarkdownEngineHookOptions, MarkdownRenderer, type MarkdownRendererProps, type MarkdownToken, type OutputFormat, type ReactComponentExtension, type ReactComponentRenderRule, type ReactComponentTokenProps, TokenTreeRenderer, type UseMarkdownOptions, type UseMarkdownResult, useMarkdown, useMarkdownComponents, useMarkdownDebug, useMarkdownEngine };
@@ -22,6 +22,37 @@ interface Extension {
22
22
  parseRules: ParseRule[];
23
23
  renderRules: RenderRule[];
24
24
  }
25
+ /**
26
+ * Props passed to a framework component registered on a ComponentRenderRule.
27
+ * The `children` type is intentionally `unknown` here — each framework binding
28
+ * (React, Astro, etc.) narrows it to the correct type in its own module.
29
+ */
30
+ interface ComponentTokenProps {
31
+ token: MarkdownToken;
32
+ /** Pre-rendered children (framework-specific ReactNode, Astro.slots, etc.) */
33
+ children?: unknown;
34
+ }
35
+ /**
36
+ * A render rule that optionally carries a framework component.
37
+ * The `render` function is always required as a string-output fallback used by
38
+ * HTML / Tailwind / JSON / Astro outputs. Framework renderers (React, etc.)
39
+ * will prefer `component` when present.
40
+ */
41
+ interface ComponentRenderRule extends RenderRule {
42
+ /**
43
+ * Framework component (React.ComponentType, Svelte component, etc.).
44
+ * Typed as `unknown` here; each framework module provides a narrowed variant.
45
+ */
46
+ component?: unknown;
47
+ }
48
+ /**
49
+ * An Extension that may carry framework components on its render rules.
50
+ * It is fully compatible with the base `Extension` type and can be registered
51
+ * with the engine like any regular extension — the engine only uses `render`.
52
+ */
53
+ interface ComponentExtension extends Extension {
54
+ renderRules: ComponentRenderRule[];
55
+ }
25
56
  type OutputFormat = 'html' | 'tailwind' | 'json';
26
57
  interface RendererConfig {
27
58
  format: OutputFormat;
@@ -61,6 +92,33 @@ interface ExtensionRegistration {
61
92
  conflictingRules?: string[];
62
93
  }
63
94
 
95
+ /**
96
+ * Props passed to a React component registered on a ReactComponentRenderRule.
97
+ */
98
+ interface ReactComponentTokenProps {
99
+ token: MarkdownToken;
100
+ /** Rendered children as React nodes (nil when the token has no children) */
101
+ children?: React.ReactNode;
102
+ }
103
+ /**
104
+ * A render rule that optionally carries a React component.
105
+ */
106
+ interface ReactComponentRenderRule {
107
+ type: string;
108
+ /** React component used by the React renderer when present. */
109
+ component?: React.ComponentType<ReactComponentTokenProps>;
110
+ /** String fallback — used by HTML / Tailwind / Astro renderers. */
111
+ render: (token: MarkdownToken) => string;
112
+ }
113
+ /**
114
+ * A ComponentExtension with React components attached to its render rules.
115
+ * Compatible with the base Extension interface — pass directly to createEngine().
116
+ */
117
+ interface ReactComponentExtension {
118
+ name: string;
119
+ parseRules: ParseRule[];
120
+ renderRules: ReactComponentRenderRule[];
121
+ }
64
122
  /**
65
123
  * Props for the MarkdownRenderer component
66
124
  */
@@ -89,6 +147,12 @@ interface MarkdownRendererProps {
89
147
  onError?: (error: Error) => void;
90
148
  /** Custom extensions to register */
91
149
  extensions?: Extension[];
150
+ /**
151
+ * Component extensions — extensions whose render rules carry React components.
152
+ * When provided the renderer switches from dangerouslySetInnerHTML to a React
153
+ * element tree, giving component-rendered tokens full React lifecycle support.
154
+ */
155
+ componentExtensions?: ReactComponentExtension[];
92
156
  /** Whether to sanitize HTML output */
93
157
  sanitize?: boolean;
94
158
  /** Allow unsafe HTML (use with caution) */
@@ -106,6 +170,8 @@ interface UseMarkdownOptions {
106
170
  debug?: boolean;
107
171
  /** Custom extensions */
108
172
  extensions?: Extension[];
173
+ /** Component extensions (React-component-bearing extensions) */
174
+ componentExtensions?: ReactComponentExtension[];
109
175
  /** Debounce delay in milliseconds */
110
176
  debounceMs?: number;
111
177
  /** Whether to memoize results */
@@ -129,6 +195,11 @@ interface UseMarkdownResult {
129
195
  render: (content: string) => void;
130
196
  /** Clear current state */
131
197
  clear: () => void;
198
+ /**
199
+ * Render an arbitrary batch of tokens to an HTML string using the current engine.
200
+ * Used internally by TokenTreeRenderer to render non-component token groups.
201
+ */
202
+ renderBatch: (tokens: MarkdownToken[]) => string;
132
203
  }
133
204
  /**
134
205
  * Options for useMarkdownEngine hook
@@ -168,11 +239,37 @@ interface MarkdownDebugInfo {
168
239
  * />
169
240
  * ```
170
241
  */
171
- declare function MarkdownRenderer({ content, className, config, format, as: Component, wrapperProps, debug, errorFallback, loading, onRender, onError, extensions, sanitize, allowUnsafeHtml, ...restProps }: MarkdownRendererProps): react_jsx_runtime.JSX.Element;
242
+ declare function MarkdownRenderer({ content, className, config, format, as: Component, wrapperProps, debug, errorFallback, loading, onRender, onError, extensions, componentExtensions, sanitize, allowUnsafeHtml, ...restProps }: MarkdownRendererProps): react_jsx_runtime.JSX.Element;
172
243
  declare namespace MarkdownRenderer {
173
244
  var displayName: string;
174
245
  }
175
246
 
247
+ interface TokenTreeRendererProps {
248
+ tokens: MarkdownToken[];
249
+ /**
250
+ * Map from token type → the rule that carries a React component.
251
+ * Only rules that actually have a `component` field should be in this map.
252
+ */
253
+ componentMap: Map<string, ReactComponentRenderRule>;
254
+ /**
255
+ * Renders a batch of tokens to an HTML string using the engine.
256
+ * Used for all tokens that don't have a React component attached.
257
+ */
258
+ renderBatch: (tokens: MarkdownToken[]) => string;
259
+ }
260
+ /**
261
+ * TokenTreeRenderer — walks a token array and renders each token as either:
262
+ * • A React component (when the token type has a `component` registered)
263
+ * • A raw HTML chunk via `dangerouslySetInnerHTML` (everything else)
264
+ *
265
+ * Consecutive non-component tokens are batched into a single HTML chunk so that
266
+ * list-grouping and other renderer logic (which operates on slices) still works.
267
+ *
268
+ * ⚠ Limitation: list items must not be interleaved with component tokens —
269
+ * the list-grouping in MarkdownRenderer operates on contiguous slices.
270
+ */
271
+ declare function TokenTreeRenderer({ tokens, componentMap, renderBatch }: TokenTreeRendererProps): React.ReactElement;
272
+
176
273
  interface CacheStats {
177
274
  size: number;
178
275
  capacity: number;
@@ -280,5 +377,39 @@ declare function useMarkdownDebug(content: string): {
280
377
  contentLength: number;
281
378
  };
282
379
  };
380
+ /**
381
+ * Hook for rendering markdown with React component extensions (TipTap-style).
382
+ *
383
+ * Returns the same values as `useMarkdown` plus:
384
+ * - `componentMap` — Map<tokenType, ReactComponentRenderRule> for TokenTreeRenderer
385
+ *
386
+ * @example
387
+ * ```tsx
388
+ * const CardExtension: ReactComponentExtension = {
389
+ * name: 'card',
390
+ * parseRules: [{ name: 'card', pattern: /:::card\n([\s\S]*?)\n:::/, render: (m) => ({ type: 'card', content: m[1] ?? '', raw: m[0] ?? '' }) }],
391
+ * renderRules: [{
392
+ * type: 'card',
393
+ * component: ({ token, children }) => <div className="card">{children}</div>,
394
+ * render: (token) => `<div class="card">${token.content}</div>`
395
+ * }]
396
+ * };
397
+ *
398
+ * const { tokens, componentMap, renderBatch } = useMarkdownComponents(content, { componentExtensions: [CardExtension] });
399
+ * ```
400
+ */
401
+ declare function useMarkdownComponents(content: string, options?: UseMarkdownOptions & {
402
+ componentExtensions?: ReactComponentExtension[];
403
+ }): {
404
+ componentMap: Map<string, ReactComponentRenderRule>;
405
+ html: string;
406
+ tokens: MarkdownToken[];
407
+ isLoading: boolean;
408
+ error: Error | null;
409
+ debug: MarkdownDebugInfo | null;
410
+ render: (content: string) => void;
411
+ clear: () => void;
412
+ renderBatch: (tokens: MarkdownToken[]) => string;
413
+ };
283
414
 
284
- export { type EngineConfig, type Extension, type MarkdownDebugInfo, type MarkdownEngineHookOptions, MarkdownRenderer, type MarkdownRendererProps, type MarkdownToken, type OutputFormat, type UseMarkdownOptions, type UseMarkdownResult, useMarkdown, useMarkdownDebug, useMarkdownEngine };
415
+ export { type ComponentExtension, type ComponentRenderRule, type ComponentTokenProps, type EngineConfig, type Extension, type MarkdownDebugInfo, type MarkdownEngineHookOptions, MarkdownRenderer, type MarkdownRendererProps, type MarkdownToken, type OutputFormat, type ReactComponentExtension, type ReactComponentRenderRule, type ReactComponentTokenProps, TokenTreeRenderer, type UseMarkdownOptions, type UseMarkdownResult, useMarkdown, useMarkdownComponents, useMarkdownDebug, useMarkdownEngine };
@@ -31,7 +31,9 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
33
  MarkdownRenderer: () => MarkdownRenderer2,
34
+ TokenTreeRenderer: () => TokenTreeRenderer,
34
35
  useMarkdown: () => useMarkdown,
36
+ useMarkdownComponents: () => useMarkdownComponents,
35
37
  useMarkdownDebug: () => useMarkdownDebug,
36
38
  useMarkdownEngine: () => useMarkdownEngine
37
39
  });
@@ -2141,9 +2143,14 @@ function useMarkdown(initialContent = "", options = {}) {
2141
2143
  newEngine.registerExtension(extension);
2142
2144
  });
2143
2145
  }
2146
+ if (options.componentExtensions) {
2147
+ options.componentExtensions.forEach((ext) => {
2148
+ newEngine.registerExtension(ext);
2149
+ });
2150
+ }
2144
2151
  engineRef.current = newEngine;
2145
2152
  return newEngine;
2146
- }, [options.config, options.format, options.debug, options.extensions]);
2153
+ }, [options.config, options.format, options.debug, options.extensions, options.componentExtensions]);
2147
2154
  const processMarkdown = (0, import_react.useCallback)((markdownContent) => {
2148
2155
  if (!markdownContent.trim()) {
2149
2156
  setHtml("");
@@ -2195,6 +2202,9 @@ function useMarkdown(initialContent = "", options = {}) {
2195
2202
  setError(null);
2196
2203
  setDebug(null);
2197
2204
  }, []);
2205
+ const renderBatch = (0, import_react.useCallback)((batch) => {
2206
+ return engine.render(batch);
2207
+ }, [engine]);
2198
2208
  return {
2199
2209
  html,
2200
2210
  tokens,
@@ -2202,7 +2212,8 @@ function useMarkdown(initialContent = "", options = {}) {
2202
2212
  error,
2203
2213
  debug,
2204
2214
  render,
2205
- clear
2215
+ clear,
2216
+ renderBatch
2206
2217
  };
2207
2218
  }
2208
2219
  function useMarkdownEngine(options = {}) {
@@ -2280,9 +2291,74 @@ function useMarkdownDebug(content) {
2280
2291
  }
2281
2292
  };
2282
2293
  }
2294
+ function useMarkdownComponents(content, options = {}) {
2295
+ const result = useMarkdown(content, options);
2296
+ const componentMap = (0, import_react.useMemo)(() => {
2297
+ const map = /* @__PURE__ */ new Map();
2298
+ if (options.componentExtensions) {
2299
+ for (const ext of options.componentExtensions) {
2300
+ for (const rule of ext.renderRules) {
2301
+ if (rule.component) {
2302
+ map.set(rule.type, rule);
2303
+ }
2304
+ }
2305
+ }
2306
+ }
2307
+ return map;
2308
+ }, [options.componentExtensions]);
2309
+ return { ...result, componentMap };
2310
+ }
2283
2311
 
2284
- // src/react/MarkdownRenderer.tsx
2312
+ // src/react/ComponentRenderer.tsx
2285
2313
  var import_jsx_runtime = require("react/jsx-runtime");
2314
+ function TokenTreeRenderer({
2315
+ tokens,
2316
+ componentMap,
2317
+ renderBatch
2318
+ }) {
2319
+ const elements = [];
2320
+ let htmlBuffer = [];
2321
+ let keyCounter = 0;
2322
+ const flushBuffer = () => {
2323
+ if (htmlBuffer.length === 0) return;
2324
+ const html = renderBatch(htmlBuffer);
2325
+ elements.push(
2326
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2327
+ "div",
2328
+ {
2329
+ dangerouslySetInnerHTML: { __html: html }
2330
+ },
2331
+ `html-${keyCounter++}`
2332
+ )
2333
+ );
2334
+ htmlBuffer = [];
2335
+ };
2336
+ for (const token of tokens) {
2337
+ const rule = componentMap.get(token.type);
2338
+ if (rule?.component) {
2339
+ flushBuffer();
2340
+ const Component = rule.component;
2341
+ const childElements = token.children && token.children.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2342
+ TokenTreeRenderer,
2343
+ {
2344
+ tokens: token.children,
2345
+ componentMap,
2346
+ renderBatch
2347
+ }
2348
+ ) : void 0;
2349
+ elements.push(
2350
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Component, { token, children: childElements }, `comp-${keyCounter++}`)
2351
+ );
2352
+ } else {
2353
+ htmlBuffer.push(token);
2354
+ }
2355
+ }
2356
+ flushBuffer();
2357
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: elements });
2358
+ }
2359
+
2360
+ // src/react/MarkdownRenderer.tsx
2361
+ var import_jsx_runtime2 = require("react/jsx-runtime");
2286
2362
  function MarkdownRenderer2({
2287
2363
  content,
2288
2364
  className,
@@ -2296,6 +2372,7 @@ function MarkdownRenderer2({
2296
2372
  onRender,
2297
2373
  onError,
2298
2374
  extensions,
2375
+ componentExtensions,
2299
2376
  sanitize = true,
2300
2377
  allowUnsafeHtml = false,
2301
2378
  ...restProps
@@ -2333,9 +2410,20 @@ function MarkdownRenderer2({
2333
2410
  if (extensions) {
2334
2411
  options.extensions = extensions;
2335
2412
  }
2413
+ if (componentExtensions) {
2414
+ options.componentExtensions = componentExtensions;
2415
+ }
2336
2416
  return options;
2337
- }, [config, format, debug, extensions, sanitize, allowUnsafeHtml]);
2338
- const { html, tokens, isLoading, error } = useMarkdown(content, markdownOptions);
2417
+ }, [config, format, debug, extensions, componentExtensions, sanitize, allowUnsafeHtml]);
2418
+ const hasComponentExtensions = componentExtensions && componentExtensions.length > 0;
2419
+ const {
2420
+ html,
2421
+ tokens,
2422
+ isLoading,
2423
+ error,
2424
+ renderBatch,
2425
+ componentMap
2426
+ } = useMarkdownComponents(content, markdownOptions);
2339
2427
  (0, import_react2.useEffect)(() => {
2340
2428
  if (html && onRender) {
2341
2429
  onRender(html, tokens);
@@ -2347,21 +2435,36 @@ function MarkdownRenderer2({
2347
2435
  }
2348
2436
  }, [error, onError]);
2349
2437
  if (isLoading && loading) {
2350
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: loading });
2438
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: loading });
2351
2439
  }
2352
2440
  if (error) {
2353
2441
  if (errorFallback) {
2354
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: errorFallback(error) });
2442
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: errorFallback(error) });
2355
2443
  }
2356
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "changerawr-error bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded", children: [
2357
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "font-medium", children: "Markdown Render Error" }),
2358
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "text-sm mt-1", children: error.message })
2444
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "changerawr-error bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded", children: [
2445
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "font-medium", children: "Markdown Render Error" }),
2446
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "text-sm mt-1", children: error.message })
2359
2447
  ] });
2360
2448
  }
2449
+ const wrapperClassName = className ? `${className} changerawr-markdown` : "changerawr-markdown";
2450
+ if (hasComponentExtensions && componentMap.size > 0) {
2451
+ return import_react2.default.createElement(
2452
+ Component,
2453
+ { ...wrapperProps, ...restProps, className: wrapperClassName },
2454
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2455
+ TokenTreeRenderer,
2456
+ {
2457
+ tokens,
2458
+ componentMap,
2459
+ renderBatch
2460
+ }
2461
+ )
2462
+ );
2463
+ }
2361
2464
  const finalWrapperProps = {
2362
2465
  ...wrapperProps,
2363
2466
  ...restProps,
2364
- className: className ? `${className} changerawr-markdown` : "changerawr-markdown",
2467
+ className: wrapperClassName,
2365
2468
  dangerouslySetInnerHTML: { __html: html }
2366
2469
  };
2367
2470
  return import_react2.default.createElement(Component, finalWrapperProps);
@@ -2383,10 +2486,10 @@ var MarkdownErrorBoundary = class extends import_react2.default.Component {
2383
2486
  if (this.props.fallback) {
2384
2487
  return this.props.fallback(this.state.error);
2385
2488
  }
2386
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "changerawr-error-boundary bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded", children: [
2387
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "font-medium", children: "Markdown Component Error" }),
2388
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "text-sm mt-1", children: this.state.error.message }),
2389
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2489
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "changerawr-error-boundary bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded", children: [
2490
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "font-medium", children: "Markdown Component Error" }),
2491
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "text-sm mt-1", children: this.state.error.message }),
2492
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2390
2493
  "button",
2391
2494
  {
2392
2495
  onClick: () => this.setState({ hasError: false, error: null }),
@@ -2402,7 +2505,9 @@ var MarkdownErrorBoundary = class extends import_react2.default.Component {
2402
2505
  // Annotate the CommonJS export names for ESM import in node:
2403
2506
  0 && (module.exports = {
2404
2507
  MarkdownRenderer,
2508
+ TokenTreeRenderer,
2405
2509
  useMarkdown,
2510
+ useMarkdownComponents,
2406
2511
  useMarkdownDebug,
2407
2512
  useMarkdownEngine
2408
2513
  });