@stream-mdx/react 0.4.0 → 1.0.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.
Files changed (106) hide show
  1. package/CHANGELOG.md +385 -4
  2. package/README.md +141 -16
  3. package/dist/blocks-renderer-DAOBFBfi.d.ts +29 -0
  4. package/dist/blocks-renderer-DQEL1kJW.d.cts +29 -0
  5. package/dist/components/bottom-stick-scroll-area.cjs +268 -0
  6. package/dist/components/bottom-stick-scroll-area.d.cts +36 -0
  7. package/dist/components/bottom-stick-scroll-area.d.ts +36 -0
  8. package/dist/components/bottom-stick-scroll-area.mjs +249 -0
  9. package/dist/components/diff-renderer.cjs +86 -0
  10. package/dist/components/diff-renderer.d.cts +11 -0
  11. package/dist/components/diff-renderer.d.ts +11 -0
  12. package/dist/components/diff-renderer.mjs +66 -0
  13. package/dist/components/index.cjs +2879 -1295
  14. package/dist/components/index.d.cts +9 -2
  15. package/dist/components/index.d.ts +9 -2
  16. package/dist/components/index.mjs +2842 -1277
  17. package/dist/components/katex-math.cjs +220 -0
  18. package/dist/components/katex-math.d.cts +10 -0
  19. package/dist/components/katex-math.d.ts +10 -0
  20. package/dist/components/katex-math.mjs +188 -0
  21. package/dist/contexts/math-tracker.cjs +240 -521
  22. package/dist/contexts/math-tracker.mjs +235 -513
  23. package/dist/index-Cmne1yUK.d.cts +723 -0
  24. package/dist/index-D7dyyu3q.d.ts +723 -0
  25. package/dist/index.cjs +13180 -5640
  26. package/dist/index.d.cts +72 -40
  27. package/dist/index.d.ts +72 -40
  28. package/dist/index.mjs +12587 -5038
  29. package/dist/math/display-wrapper.cjs +7 -15
  30. package/dist/math/display-wrapper.d.cts +2 -2
  31. package/dist/math/display-wrapper.d.ts +2 -2
  32. package/dist/math/display-wrapper.mjs +2 -6
  33. package/dist/node-views-Bpc6Gkgl.d.cts +13 -0
  34. package/dist/node-views-DpnYdaAS.d.ts +13 -0
  35. package/dist/renderer/hooks.cjs +35 -20
  36. package/dist/renderer/hooks.d.cts +22 -1
  37. package/dist/renderer/hooks.d.ts +22 -1
  38. package/dist/renderer/hooks.mjs +27 -12
  39. package/dist/renderer/patch-batching.cjs +7 -8
  40. package/dist/renderer/patch-batching.d.cts +1 -1
  41. package/dist/renderer/patch-batching.d.ts +1 -1
  42. package/dist/renderer/patch-batching.mjs +2 -1
  43. package/dist/renderer/patch-coalescing.cjs +5 -8
  44. package/dist/renderer/patch-commit-scheduler.cjs +160 -326
  45. package/dist/renderer/patch-commit-scheduler.d.cts +28 -4
  46. package/dist/renderer/patch-commit-scheduler.d.ts +28 -4
  47. package/dist/renderer/patch-commit-scheduler.mjs +157 -318
  48. package/dist/renderer/store.cjs +3847 -1099
  49. package/dist/renderer/store.d.cts +111 -3
  50. package/dist/renderer/store.d.ts +111 -3
  51. package/dist/renderer/store.mjs +3847 -1085
  52. package/dist/renderer/virtualized-code.cjs +415 -71
  53. package/dist/renderer/virtualized-code.d.cts +24 -23
  54. package/dist/renderer/virtualized-code.d.ts +24 -23
  55. package/dist/renderer/virtualized-code.mjs +409 -62
  56. package/dist/renderer.cjs +10939 -3974
  57. package/dist/renderer.d.cts +100 -29
  58. package/dist/renderer.d.ts +100 -29
  59. package/dist/renderer.mjs +10711 -3697
  60. package/dist/server-regions-CPLHGli0.d.cts +32 -0
  61. package/dist/server-regions-mUhyoKi7.d.ts +32 -0
  62. package/dist/server.cjs +10286 -3365
  63. package/dist/server.d.cts +17 -247
  64. package/dist/server.d.ts +17 -247
  65. package/dist/server.mjs +10565 -3619
  66. package/dist/{streaming-markdown-DOc2uLiP.d.ts → streaming-markdown-yELbJxyH.d.ts} +51 -55
  67. package/dist/{streaming-markdown-D-JcByd9.d.cts → streaming-markdown-zVwFuaJG.d.cts} +51 -55
  68. package/dist/streaming-markdown.cjs +11828 -4879
  69. package/dist/streaming-markdown.d.cts +9 -2
  70. package/dist/streaming-markdown.d.ts +9 -2
  71. package/dist/streaming-markdown.mjs +11820 -4821
  72. package/dist/trusted-mdx/index.cjs +833 -0
  73. package/dist/trusted-mdx/index.d.cts +11 -0
  74. package/dist/trusted-mdx/index.d.ts +11 -0
  75. package/dist/trusted-mdx/index.mjs +807 -0
  76. package/dist/trusted-mdx/native-children-slot.cjs +45 -0
  77. package/dist/trusted-mdx/native-children-slot.d.cts +11 -0
  78. package/dist/trusted-mdx/native-children-slot.d.ts +11 -0
  79. package/dist/trusted-mdx/native-children-slot.mjs +24 -0
  80. package/dist/trusted-mdx/server.cjs +752 -0
  81. package/dist/trusted-mdx/server.d.cts +24 -0
  82. package/dist/trusted-mdx/server.d.ts +24 -0
  83. package/dist/trusted-mdx/server.mjs +724 -0
  84. package/dist/trusted-mdx-contract-ElKpkDI5.d.cts +113 -0
  85. package/dist/trusted-mdx-contract-ElKpkDI5.d.ts +113 -0
  86. package/dist/utils/inline-html.cjs +246 -147
  87. package/dist/utils/inline-html.d.cts +39 -6
  88. package/dist/utils/inline-html.d.ts +39 -6
  89. package/dist/utils/inline-html.mjs +237 -134
  90. package/package.json +52 -18
  91. package/shadcn/README.md +28 -0
  92. package/shadcn/bottom-stick-scroll-area.tsx +358 -0
  93. package/dist/index-D2MYusAM.d.cts +0 -248
  94. package/dist/index-D2MYusAM.d.ts +0 -248
  95. package/dist/mdx-client.cjs +0 -408
  96. package/dist/mdx-client.d.cts +0 -113
  97. package/dist/mdx-client.d.ts +0 -113
  98. package/dist/mdx-client.mjs +0 -369
  99. package/dist/mdx-coordinator.cjs +0 -563
  100. package/dist/mdx-coordinator.d.cts +0 -21
  101. package/dist/mdx-coordinator.d.ts +0 -21
  102. package/dist/mdx-coordinator.mjs +0 -529
  103. package/dist/renderer/node-views.cjs +0 -1642
  104. package/dist/renderer/node-views.d.cts +0 -12
  105. package/dist/renderer/node-views.d.ts +0 -12
  106. package/dist/renderer/node-views.mjs +0 -1616
@@ -2,86 +2,169 @@
2
2
 
3
3
  // src/utils/inline-html.ts
4
4
  import React from "react";
5
- var BACKTICK_CONTENT = /^`([\s\S]+)`$/;
6
- var DEFAULT_INLINE_HTML_RENDERERS = {
5
+ import { defaultUrlTransform, isForbiddenAttribute, isSafeStyleDeclaration } from "@stream-mdx/core";
6
+ function renderHtmlInlineNode(node, children, elements, key, textValue, urlTransform, filterElement, parentTagName, renderPresentation, renderNativeElement) {
7
+ if (node.isClose || node.tagName === "script" || node.tagName === "style") return null;
8
+ if (!node.tagName || node.sanitized === "")
9
+ return React.createElement(React.Fragment, { key }, children);
10
+ let tag = node.tagName.toLowerCase(), props = mapAttributesToProps(node.attributes ?? {}, urlTransform);
11
+ props.key = key;
12
+ let renderElement = () => {
13
+ let nativeView;
14
+ return renderNativeElement ? nativeView = renderNativeElement(
15
+ tag,
16
+ props,
17
+ node.isSelfClosing ? void 0 : children,
18
+ key ?? tag
19
+ ) : tag === "textarea" ? (props.defaultValue = textValue ?? "", nativeView = React.createElement(elements?.[tag] ?? tag, props)) : nativeView = React.createElement(elements?.[tag] ?? tag, props, node.isSelfClosing ? void 0 : children), renderPresentation ? renderPresentation(tag, props, nativeView, children, key ?? tag) : nativeView;
20
+ };
21
+ return filterElement ? filterElement(
22
+ { tagName: tag, properties: props },
23
+ { source: "html", parentTagName },
24
+ renderElement,
25
+ () => children
26
+ ) : renderElement();
27
+ }
28
+ function renderHtmlOwnershipTree(nodes, elements, renderBlock, renderInline, renderInlineElement, renderParagraph, urlTransform, filterElement, parentTagName, contentComponents, renderPresentation, parentPath = "html", renderComponentPresentation) {
29
+ return nodes.map((node, index) => {
30
+ let nodeKey = `${parentPath}:${index}`;
31
+ if (node.kind === "text") return node.inline && renderInline ? renderInline(node.inline, parentTagName) : node.rawSource ?? node.text;
32
+ if (node.kind === "block") return renderBlock(node.block, parentTagName);
33
+ if (node.kind === "component") {
34
+ let reg = contentComponents?.[node.name];
35
+ if (!reg)
36
+ return React.createElement(React.Fragment, { key: nodeKey }, node.rawSource ?? `<${node.name} />`);
37
+ let result = reg.render({
38
+ props: node.props,
39
+ key: index,
40
+ hasChildren: node.children.length > 0,
41
+ renderChildren: () => renderHtmlOwnershipTree(
42
+ node.children,
43
+ elements,
44
+ renderBlock,
45
+ renderInline,
46
+ renderInlineElement,
47
+ renderParagraph,
48
+ urlTransform,
49
+ filterElement,
50
+ node.name,
51
+ contentComponents,
52
+ renderPresentation,
53
+ nodeKey,
54
+ renderComponentPresentation
55
+ ),
56
+ status: node.status,
57
+ diagnostic: node.diagnostic
58
+ });
59
+ return result.kind === "invalid" ? React.createElement(React.Fragment, { key: nodeKey }, node.rawSource ?? `<${node.name} />`) : result.kind === "suppressed" ? null : renderComponentPresentation ? renderComponentPresentation(
60
+ node.name,
61
+ result.element,
62
+ React.isValidElement(result.element) ? result.element.props.children : void 0,
63
+ nodeKey,
64
+ node.status,
65
+ node.rawSource
66
+ ) : result.element;
67
+ }
68
+ let tag = node.tagName.toLowerCase(), props = mapAttributesToProps(node.attributes, urlTransform);
69
+ props.key = nodeKey;
70
+ let children = renderHtmlOwnershipTree(node.children, elements, renderBlock, renderInline, renderInlineElement, renderParagraph, urlTransform, filterElement, tag, contentComponents, renderPresentation, nodeKey, renderComponentPresentation);
71
+ if (node.paragraphRoot && renderParagraph) {
72
+ let paragraph = filterElement ? filterElement(
73
+ { tagName: "p", properties: props, blockType: "paragraph" },
74
+ { source: "markdown", parentTagName },
75
+ () => renderParagraph(children),
76
+ () => children
77
+ ) : renderParagraph(children);
78
+ return React.createElement(React.Fragment, { key: nodeKey }, paragraph);
79
+ }
80
+ if (node.inlineNode && renderInlineElement)
81
+ return React.createElement(React.Fragment, { key: nodeKey }, renderInlineElement(node.inlineNode, children, parentTagName, nodeKey));
82
+ let renderElement = () => {
83
+ let nativeView;
84
+ if (tag === "textarea")
85
+ props.defaultValue = node.children.map((child) => child.kind === "text" ? child.text : "").join(""), nativeView = React.createElement(elements[tag] ?? tag, props);
86
+ else {
87
+ let content = tag === "pre" && node.children.every((child) => child.kind === "text" && !child.inline) ? node.children.map((child) => child.kind === "text" ? child.text : "").join("") : children;
88
+ nativeView = React.createElement(elements[tag] ?? tag, props, node.children.length ? content : void 0);
89
+ }
90
+ return renderPresentation ? renderPresentation(tag, props, nativeView, children, nodeKey) : nativeView;
91
+ };
92
+ return filterElement ? filterElement(
93
+ { tagName: tag, properties: props },
94
+ { source: "html", parentTagName },
95
+ renderElement,
96
+ () => children
97
+ ) : renderElement();
98
+ });
99
+ }
100
+ var BACKTICK_CONTENT = /^`([\s\S]+)`$/, DEFAULT_INLINE_HTML_RENDERERS = {
7
101
  kbd: (descriptor, ctx) => {
8
- const trimmed = descriptor.rawInner.trim();
9
- const match = BACKTICK_CONTENT.exec(trimmed);
102
+ let trimmed = descriptor.rawInner.trim(), match = BACKTICK_CONTENT.exec(trimmed);
10
103
  if (match) {
11
- const codeText = decodeHtmlEntities(match[1]);
12
- const props = mapAttributesToProps(descriptor.attributes);
13
- if (ctx.key !== void 0 && ctx.key !== null) {
14
- props.key = ctx.key;
15
- }
16
- return React.createElement("kbd", props, React.createElement("code", { className: "inline-code" }, codeText));
104
+ let codeText = decodeHtmlEntities(match[1]), props = mapAttributesToProps(descriptor.attributes);
105
+ return ctx.key !== void 0 && ctx.key !== null && (props.key = ctx.key), React.createElement("kbd", props, React.createElement("code", { className: "inline-code" }, codeText));
17
106
  }
18
107
  return ctx.defaultRender();
19
108
  }
20
109
  };
21
110
  function renderInlineHtmlSegment(raw, sanitized, options = {}) {
22
- const descriptor = parseInlineHtmlDescriptor(raw, sanitized);
23
- if (!descriptor) {
111
+ let descriptor = parseInlineHtmlDescriptor(raw, sanitized);
112
+ if (!descriptor)
24
113
  return null;
25
- }
26
- const className = options.className ?? "markdown-inline-html";
27
- const isInlineTag = INLINE_ELEMENTS.has(descriptor.tagName);
28
- const defaultRender = () => {
29
- if (!descriptor.sanitized || descriptor.sanitized.length === 0) {
30
- if (!descriptor.text) {
31
- return null;
32
- }
33
- if (options.key !== void 0 && options.key !== null) {
34
- return React.createElement(React.Fragment, { key: options.key }, descriptor.text);
35
- }
36
- return React.createElement(React.Fragment, {}, descriptor.text);
37
- }
114
+ let className = options.className ?? "markdown-inline-html", hasCustomElement = !!options.htmlElements?.[descriptor.tagName], isInlineTag = INLINE_ELEMENTS.has(descriptor.tagName) || hasCustomElement, defaultRender = () => {
115
+ if (!descriptor.sanitized || descriptor.sanitized.length === 0)
116
+ return descriptor.text ? options.key !== void 0 && options.key !== null ? React.createElement(React.Fragment, { key: options.key }, descriptor.text) : React.createElement(React.Fragment, {}, descriptor.text) : null;
38
117
  if (isInlineTag) {
39
- const parsedElement = renderSanitizedHtmlTree(descriptor.sanitized, options.key);
40
- if (parsedElement) {
118
+ let parsedElement = renderSanitizedHtmlTree(
119
+ descriptor.sanitized,
120
+ options.key,
121
+ options.htmlElements,
122
+ options.literalTagContent,
123
+ options.parseMarkdown
124
+ );
125
+ if (parsedElement)
41
126
  return parsedElement;
42
- }
43
127
  }
44
- const props = {
128
+ let customElementComp = options.htmlElements?.[descriptor.tagName];
129
+ if (customElementComp) {
130
+ let props2 = mapAttributesToProps(descriptor.attributes, options.urlTransform);
131
+ return options.key !== void 0 && options.key !== null && (props2.key = options.key), React.createElement(customElementComp, props2, descriptor.text || descriptor.sanitizedInner || void 0);
132
+ }
133
+ let props = {
45
134
  className,
46
135
  dangerouslySetInnerHTML: { __html: descriptor.sanitized }
47
136
  };
48
- if (options.key !== void 0 && options.key !== null) {
49
- props.key = options.key;
50
- }
51
- return React.createElement("span", props);
137
+ return options.key !== void 0 && options.key !== null && (props.key = options.key), React.createElement("span", props);
52
138
  };
53
- const allRenderers = options.renderers ? { ...DEFAULT_INLINE_HTML_RENDERERS, ...options.renderers } : DEFAULT_INLINE_HTML_RENDERERS;
54
- const renderer = allRenderers[descriptor.tagName];
55
- if (renderer) {
56
- const rendered = renderer(descriptor, { key: options.key, defaultRender });
57
- if (rendered) {
58
- if (options.key !== void 0 && options.key !== null && React.isValidElement(rendered) && rendered.key === null) {
59
- return React.cloneElement(rendered, { key: options.key });
60
- }
61
- return rendered;
139
+ if (!hasCustomElement) {
140
+ let renderer = (options.renderers ? { ...DEFAULT_INLINE_HTML_RENDERERS, ...options.renderers } : DEFAULT_INLINE_HTML_RENDERERS)[descriptor.tagName];
141
+ if (renderer) {
142
+ let rendered = renderer(descriptor, { key: options.key, defaultRender });
143
+ if (rendered)
144
+ return options.key !== void 0 && options.key !== null && React.isValidElement(rendered) && rendered.key === null ? React.cloneElement(rendered, { key: options.key }) : rendered;
62
145
  }
63
146
  }
64
147
  return defaultRender();
65
148
  }
66
149
  function parseInlineHtmlDescriptor(raw, sanitized) {
67
- const normalizedRaw = typeof raw === "string" ? raw.trim() : "";
68
- const normalizedSanitized = typeof sanitized === "string" && sanitized.length > 0 ? sanitized.trim() : normalizedRaw;
69
- if (!normalizedSanitized) {
70
- return null;
71
- }
72
- const match = normalizedSanitized.match(/^<([A-Za-z][\w:-]*)(\s[^>]*)?>([\s\S]*)<\/\1>$/);
73
- if (!match) {
150
+ let normalizedRaw = typeof raw == "string" ? raw.trim() : "", normalizedSanitized = typeof sanitized == "string" && sanitized.length > 0 ? sanitized.trim() : normalizedRaw;
151
+ if (!normalizedSanitized)
74
152
  return null;
153
+ let pairedMatch = normalizedSanitized.match(/^<([A-Za-z][\w:-]*)((?:\s+[^>]*)?)>([\s\S]*)<\/\1>$/), tagNameRaw, attrString = "", sanitizedInner = "";
154
+ if (pairedMatch)
155
+ tagNameRaw = pairedMatch[1], attrString = pairedMatch[2] ?? "", sanitizedInner = pairedMatch[3] ?? "";
156
+ else {
157
+ let selfClosingMatch = normalizedSanitized.match(/^<([A-Za-z][\w:-]*)((?:\s+[^>]*)?)\s*(?:\/?>|>)$/) || normalizedRaw.match(/^<([A-Za-z][\w:-]*)((?:\s+[^>]*)?)\s*(?:\/?>|>)$/);
158
+ if (!selfClosingMatch)
159
+ return null;
160
+ tagNameRaw = selfClosingMatch[1], attrString = selfClosingMatch[2] ?? "", sanitizedInner = "";
75
161
  }
76
- const [, tagNameRaw, attrString = "", sanitizedInner = ""] = match;
77
- const tagName = tagNameRaw.toLowerCase();
78
- const attributes = parseAttributes(attrString);
79
- let rawInner = "";
162
+ let tagName = tagNameRaw.toLowerCase(), attributes = parseAttributes(attrString), rawInner = "";
80
163
  if (normalizedRaw) {
81
- const rawMatch = normalizedRaw.match(/^<([A-Za-z][\w:-]*)(\s[^>]*)?>([\s\S]*)<\/\1>$/);
164
+ let rawMatch = normalizedRaw.match(/^<([A-Za-z][\w:-]*)(\s[^>]*)?>([\s\S]*)<\/\1>$/);
82
165
  rawInner = rawMatch ? rawMatch[3] ?? "" : normalizedRaw;
83
166
  }
84
- const text = extractPlainText(rawInner || sanitizedInner);
167
+ let text = extractPlainText(rawInner || sanitizedInner);
85
168
  return {
86
169
  tagName,
87
170
  attributes,
@@ -93,52 +176,91 @@ function parseInlineHtmlDescriptor(raw, sanitized) {
93
176
  };
94
177
  }
95
178
  function parseAttributes(input) {
96
- const attrs = {};
179
+ let attrs = {};
97
180
  if (!input) return attrs;
98
- const attrPattern = /([A-Za-z_:][\w:.\-]*)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/g;
99
- let match = attrPattern.exec(input);
100
- while (match !== null) {
101
- const name = match[1];
181
+ let attrPattern = /([A-Za-z_:][\w:.\-]*)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/g, match = attrPattern.exec(input);
182
+ for (; match !== null; ) {
183
+ let name = match[1];
102
184
  if (!name) continue;
103
- const value = match[2] ?? match[3] ?? match[4] ?? "";
104
- attrs[name] = value;
105
- match = attrPattern.exec(input);
185
+ let value = match[2] ?? match[3] ?? match[4] ?? "";
186
+ attrs[name] = value, match = attrPattern.exec(input);
106
187
  }
107
188
  return attrs;
108
189
  }
109
- function mapAttributesToProps(attrs) {
110
- const props = {};
111
- for (const [key, value] of Object.entries(attrs)) {
112
- if (key === "class") {
113
- props.className = value;
114
- } else if (key === "style") {
115
- props.style = parseStyleAttribute(value);
116
- } else if (key === "tabindex" || key === "tabIndex") {
117
- const normalized = normalizeTabIndex(value);
118
- if (normalized !== void 0) {
119
- props.tabIndex = normalized;
120
- }
121
- } else {
122
- props[key] = value;
123
- }
190
+ var BOOLEAN_HTML_PROPS = {
191
+ allowFullScreen: !0,
192
+ async: !0,
193
+ autoFocus: !0,
194
+ autoPlay: !0,
195
+ checked: !0,
196
+ controls: !0,
197
+ default: !0,
198
+ defer: !0,
199
+ disabled: !0,
200
+ formNoValidate: !0,
201
+ hidden: !0,
202
+ inert: !0,
203
+ loop: !0,
204
+ multiple: !0,
205
+ muted: !0,
206
+ noModule: !0,
207
+ noValidate: !0,
208
+ open: !0,
209
+ playsInline: !0,
210
+ readOnly: !0,
211
+ required: !0,
212
+ reversed: !0,
213
+ selected: !0,
214
+ itemScope: !0
215
+ };
216
+ function mapAttributesToProps(attrs, urlTransform) {
217
+ let props = {}, transform = urlTransform ?? defaultUrlTransform;
218
+ for (let [key, value] of Object.entries(attrs)) {
219
+ let lower = key.toLowerCase();
220
+ if (!isForbiddenAttribute(lower))
221
+ if (key === "class")
222
+ props.className = value;
223
+ else if (key === "style")
224
+ props.style = parseStyleAttribute(value);
225
+ else if (key === "tabindex" || key === "tabIndex") {
226
+ let normalized = normalizeTabIndex(value);
227
+ normalized !== void 0 && (props.tabIndex = normalized);
228
+ } else if (lower === "href") {
229
+ let safe = transform(value, "href", { source: "html" });
230
+ safe != null && (props.href = safe);
231
+ } else if (lower === "src") {
232
+ let safe = transform(value, "src", { source: "html" });
233
+ safe != null && (props.src = safe);
234
+ } else if (lower === "action") {
235
+ let safe = transform(value, "href", { source: "html" });
236
+ safe != null && (props.action = safe);
237
+ } else if (lower === "formaction") {
238
+ let safe = transform(value, "href", { source: "html" });
239
+ safe != null && (props.formAction = safe);
240
+ } else if (lower === "xlink:href" || lower === "xlinkhref") {
241
+ let safe = transform(value, "href", { source: "html" });
242
+ safe != null && (props.xlinkHref = safe);
243
+ } else /^(?:data|aria)[A-Z]/.test(key) ? props[key.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)] = value : BOOLEAN_HTML_PROPS[key] ? props[key] = !0 : props[key] = value;
124
244
  }
125
245
  return props;
126
246
  }
127
247
  function parseStyleAttribute(value) {
128
- const style = {};
248
+ let style = {};
129
249
  if (!value) return style;
130
- const pairs = value.split(";");
131
- for (const pair of pairs) {
250
+ let pairs = value.split(";");
251
+ for (let pair of pairs) {
132
252
  if (!pair.trim()) continue;
133
- const [property, rawVal] = pair.split(":");
134
- if (!property || rawVal === void 0) continue;
135
- const camelProp = property.trim().replace(/-([a-z])/g, (_, char) => char.toUpperCase());
136
- style[camelProp] = rawVal.trim();
253
+ let separator = pair.indexOf(":");
254
+ if (separator <= 0) continue;
255
+ let property = pair.slice(0, separator).trim(), rawVal = pair.slice(separator + 1).trim();
256
+ if (!isSafeStyleDeclaration(property, rawVal)) continue;
257
+ let camelProp = property.replace(/-([a-z])/g, (_, char) => char.toUpperCase());
258
+ style[camelProp] = rawVal;
137
259
  }
138
260
  return style;
139
261
  }
140
262
  function normalizeTabIndex(value) {
141
- const numeric = Number(value);
263
+ let numeric = Number(value);
142
264
  return Number.isNaN(numeric) ? void 0 : numeric;
143
265
  }
144
266
  function decodeHtmlEntities(value) {
@@ -146,76 +268,50 @@ function decodeHtmlEntities(value) {
146
268
  }
147
269
  function extractPlainText(value) {
148
270
  if (!value) return "";
149
- const withoutTags = value.replace(/<\/?[^>]+>/g, "");
271
+ let withoutTags = value.replace(/<\/?[^>]+>/g, "");
150
272
  return decodeHtmlEntities(withoutTags);
151
273
  }
152
- function renderSanitizedHtmlTree(html, key) {
153
- if (typeof DOMParser === "undefined") {
274
+ var DEFAULT_LITERAL_TAGS = /* @__PURE__ */ new Set(["code", "pre", "script", "style", "textarea"]);
275
+ function renderSanitizedHtmlTree(html, key, htmlElements, literalTagContent, parseMarkdown) {
276
+ if (typeof DOMParser > "u")
154
277
  return null;
155
- }
156
278
  try {
157
- const parser = new DOMParser();
158
- const doc = parser.parseFromString(html, "text/html");
159
- const body = doc.body;
160
- const nodes = Array.from(body.childNodes);
161
- if (nodes.length === 0) {
279
+ let body = new DOMParser().parseFromString(html, "text/html").body, nodes = Array.from(body.childNodes);
280
+ if (nodes.length === 0)
162
281
  return null;
163
- }
164
- const baseKey = typeof key === "string" || typeof key === "number" ? String(key) : "inline-html";
165
- const children = nodes.map((node, index) => convertDomNode(node, `${baseKey}-${index}`)).filter((child) => child !== null && child !== void 0);
166
- if (children.length === 0) {
282
+ let literalTags = literalTagContent ? /* @__PURE__ */ new Set([...DEFAULT_LITERAL_TAGS, ...literalTagContent.map((t) => t.toLowerCase())]) : DEFAULT_LITERAL_TAGS, baseKey = typeof key == "string" || typeof key == "number" ? String(key) : "inline-html", children = nodes.map((node, index) => convertDomNode(node, `${baseKey}-${index}`, htmlElements, literalTags, !1, parseMarkdown)).filter((child) => child != null);
283
+ if (children.length === 0)
167
284
  return null;
168
- }
169
285
  if (children.length === 1) {
170
- const [child] = children;
171
- if (React.isValidElement(child)) {
172
- if (key !== void 0 && key !== null) {
173
- return React.cloneElement(child, { key });
174
- }
175
- return child;
176
- }
177
- if (typeof child === "string" || typeof child === "number") {
178
- if (key !== void 0 && key !== null) {
179
- return React.createElement(React.Fragment, { key }, child);
180
- }
181
- return React.createElement(React.Fragment, {}, child);
182
- }
183
- return React.createElement(React.Fragment, { key }, child);
286
+ let [child] = children;
287
+ return React.isValidElement(child) ? key != null ? React.cloneElement(child, { key }) : child : typeof child == "string" || typeof child == "number" ? key != null ? React.createElement(React.Fragment, { key }, child) : React.createElement(React.Fragment, {}, child) : React.createElement(React.Fragment, { key }, child);
184
288
  }
185
289
  return React.createElement(React.Fragment, { key }, ...children);
186
290
  } catch {
187
291
  return null;
188
292
  }
189
293
  }
190
- function convertDomNode(node, key) {
294
+ function convertDomNode(node, key, htmlElements, literalTags, parentIsLiteral, parseMarkdown) {
191
295
  if (node.nodeType === Node.TEXT_NODE) {
192
- return node.textContent ?? "";
296
+ let rawText = node.textContent ?? "";
297
+ return parentIsLiteral || !parseMarkdown ? rawText : parseMarkdown(rawText);
193
298
  }
194
- if (node.nodeType !== Node.ELEMENT_NODE) {
299
+ if (node.nodeType !== Node.ELEMENT_NODE)
195
300
  return null;
196
- }
197
- const element = node;
198
- const tagName = element.tagName.toLowerCase();
199
- const attrs = getElementAttributes(element);
200
- const props = mapAttributesToProps(attrs);
301
+ let element = node, tagName = element.tagName.toLowerCase(), attrs = getElementAttributes(element), props = mapAttributesToProps(attrs);
201
302
  props.key = key;
202
- const children = [];
303
+ let isLiteral = parentIsLiteral || (literalTags ? literalTags.has(tagName) : !1), children = [];
203
304
  element.childNodes.forEach((child, index) => {
204
- const converted = convertDomNode(child, `${key}-${index}`);
205
- if (converted !== null && converted !== void 0) {
206
- children.push(converted);
207
- }
305
+ let converted = convertDomNode(child, `${key}-${index}`, htmlElements, literalTags, isLiteral, parseMarkdown);
306
+ converted != null && children.push(converted);
208
307
  });
209
- if (children.length === 0) {
210
- return React.createElement(tagName, props);
211
- }
212
- return React.createElement(tagName, props, ...children);
308
+ let customComponent = htmlElements?.[tagName];
309
+ return customComponent ? children.length === 0 ? React.createElement(customComponent, props) : React.createElement(customComponent, props, ...children) : children.length === 0 ? React.createElement(tagName, props) : React.createElement(tagName, props, ...children);
213
310
  }
214
311
  function getElementAttributes(element) {
215
- const attrs = {};
216
- for (const attr of Array.from(element.attributes)) {
312
+ let attrs = {};
313
+ for (let attr of Array.from(element.attributes))
217
314
  attrs[attr.name] = attr.value;
218
- }
219
315
  return attrs;
220
316
  }
221
317
  var INLINE_ELEMENTS = /* @__PURE__ */ new Set([
@@ -225,6 +321,11 @@ var INLINE_ELEMENTS = /* @__PURE__ */ new Set([
225
321
  "bdi",
226
322
  "bdo",
227
323
  "br",
324
+ "img",
325
+ "input",
326
+ "picture",
327
+ "del",
328
+ "ins",
228
329
  "cite",
229
330
  "code",
230
331
  "data",
@@ -270,5 +371,7 @@ var INLINE_ELEMENTS = /* @__PURE__ */ new Set([
270
371
  ]);
271
372
  export {
272
373
  DEFAULT_INLINE_HTML_RENDERERS,
374
+ renderHtmlInlineNode,
375
+ renderHtmlOwnershipTree,
273
376
  renderInlineHtmlSegment
274
377
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stream-mdx/react",
3
- "version": "0.4.0",
3
+ "version": "1.0.0",
4
4
  "description": "React renderer, scheduler, and worker wiring for the Streaming Markdown V2 pipeline",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -31,10 +31,30 @@
31
31
  "import": "./dist/server.mjs",
32
32
  "require": "./dist/server.cjs"
33
33
  },
34
+ "./components": {
35
+ "types": "./dist/components/index.d.ts",
36
+ "import": "./dist/components/index.mjs",
37
+ "require": "./dist/components/index.cjs"
38
+ },
39
+ "./components/bottom-stick-scroll-area": {
40
+ "types": "./dist/components/bottom-stick-scroll-area.d.ts",
41
+ "import": "./dist/components/bottom-stick-scroll-area.mjs",
42
+ "require": "./dist/components/bottom-stick-scroll-area.cjs"
43
+ },
44
+ "./components/diff-renderer": {
45
+ "types": "./dist/components/diff-renderer.d.ts",
46
+ "import": "./dist/components/diff-renderer.mjs",
47
+ "require": "./dist/components/diff-renderer.cjs"
48
+ },
49
+ "./components/katex-math": {
50
+ "types": "./dist/components/katex-math.d.ts",
51
+ "import": "./dist/components/katex-math.mjs",
52
+ "require": "./dist/components/katex-math.cjs"
53
+ },
34
54
  "./renderer": {
35
- "types": "./dist/renderer/index.d.ts",
36
- "import": "./dist/renderer/index.mjs",
37
- "require": "./dist/renderer/index.cjs"
55
+ "types": "./dist/renderer.d.ts",
56
+ "import": "./dist/renderer.mjs",
57
+ "require": "./dist/renderer.cjs"
38
58
  },
39
59
  "./renderer/patch-commit-scheduler": {
40
60
  "types": "./dist/renderer/patch-commit-scheduler.d.ts",
@@ -56,16 +76,6 @@
56
76
  "import": "./dist/renderer/store.mjs",
57
77
  "require": "./dist/renderer/store.cjs"
58
78
  },
59
- "./mdx-client": {
60
- "types": "./dist/mdx-client.d.ts",
61
- "import": "./dist/mdx-client.mjs",
62
- "require": "./dist/mdx-client.cjs"
63
- },
64
- "./mdx-coordinator": {
65
- "types": "./dist/mdx-coordinator.d.ts",
66
- "import": "./dist/mdx-coordinator.mjs",
67
- "require": "./dist/mdx-coordinator.cjs"
68
- },
69
79
  "./math/display-wrapper": {
70
80
  "types": "./dist/math/display-wrapper.d.ts",
71
81
  "import": "./dist/math/display-wrapper.mjs",
@@ -75,29 +85,53 @@
75
85
  "types": "./dist/contexts/math-tracker.d.ts",
76
86
  "import": "./dist/contexts/math-tracker.mjs",
77
87
  "require": "./dist/contexts/math-tracker.cjs"
88
+ },
89
+ "./trusted-mdx/server": {
90
+ "types": "./dist/trusted-mdx/server.d.ts",
91
+ "import": "./dist/trusted-mdx/server.mjs",
92
+ "require": "./dist/trusted-mdx/server.cjs"
93
+ },
94
+ "./_internal/trusted-mdx-native-children": {
95
+ "types": "./dist/trusted-mdx/native-children-slot.d.ts",
96
+ "import": "./dist/trusted-mdx/native-children-slot.mjs",
97
+ "require": "./dist/trusted-mdx/native-children-slot.cjs"
98
+ },
99
+ "./trusted-mdx": {
100
+ "types": "./dist/trusted-mdx/index.d.ts",
101
+ "import": "./dist/trusted-mdx/index.mjs",
102
+ "require": "./dist/trusted-mdx/index.cjs"
78
103
  }
79
104
  },
80
105
  "files": [
81
106
  "dist",
107
+ "shadcn",
82
108
  "README.md",
83
109
  "CHANGELOG.md"
84
110
  ],
85
111
  "sideEffects": false,
86
112
  "scripts": {
87
- "build": "tsup",
113
+ "build": "npm run clean && tsup",
88
114
  "test": "tsx ../../scripts/run-tests.ts __tests__",
89
115
  "clean": "rm -rf dist",
90
116
  "prepack": "npm run build"
91
117
  },
92
118
  "dependencies": {
93
- "@stream-mdx/core": "0.4.0",
94
- "@stream-mdx/worker": "0.4.0"
119
+ "@stream-mdx/core": "1.0.0",
120
+ "@stream-mdx/protocol": "1.0.0",
121
+ "@stream-mdx/worker": "1.0.0"
95
122
  },
96
123
  "devDependencies": {
97
- "jsdom": "^26.0.0"
124
+ "jsdom": "^26.0.0",
125
+ "postcss": "^8.4.31"
98
126
  },
99
127
  "peerDependencies": {
128
+ "katex": "^0.16.0",
100
129
  "react": ">=18.2.0",
101
130
  "react-dom": ">=18.2.0"
131
+ },
132
+ "peerDependenciesMeta": {
133
+ "katex": {
134
+ "optional": true
135
+ }
102
136
  }
103
137
  }
@@ -0,0 +1,28 @@
1
+ # ShadCN Drop-in: Bottom Stick Scroll Area
2
+
3
+ This folder contains a single-file `BottomStickScrollArea` component meant for copy/paste into app source trees (ShadCN style).
4
+
5
+ ## File
6
+
7
+ - `bottom-stick-scroll-area.tsx`
8
+
9
+ ## Copy steps
10
+
11
+ 1. Copy `bottom-stick-scroll-area.tsx` into your app, for example:
12
+ - `components/ui/bottom-stick-scroll-area.tsx`
13
+ 2. Keep/adjust these imports to match your app:
14
+ - `@/components/ui/button`
15
+ - `@/components/ui/scroll-area`
16
+ - `@/lib/utils`
17
+ 3. Ensure dependencies are installed:
18
+ - `@radix-ui/react-scroll-area`
19
+ - `lucide-react`
20
+
21
+ ## Behavior
22
+
23
+ - Sticks instantly to bottom while content streams (`STICKY_INSTANT`).
24
+ - Detaches when user scrolls upward (`DETACHED`).
25
+ - "Scroll to bottom" button appears with a short fade and performs smooth return (`RETURNING_SMOOTH`).
26
+ - Supports canceling return if the user scrolls during animation.
27
+
28
+ For package-first usage (no copy/paste), use `BottomStickScrollArea` from `@stream-mdx/react`.