@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.
- package/CHANGELOG.md +385 -4
- package/README.md +141 -16
- package/dist/blocks-renderer-DAOBFBfi.d.ts +29 -0
- package/dist/blocks-renderer-DQEL1kJW.d.cts +29 -0
- package/dist/components/bottom-stick-scroll-area.cjs +268 -0
- package/dist/components/bottom-stick-scroll-area.d.cts +36 -0
- package/dist/components/bottom-stick-scroll-area.d.ts +36 -0
- package/dist/components/bottom-stick-scroll-area.mjs +249 -0
- package/dist/components/diff-renderer.cjs +86 -0
- package/dist/components/diff-renderer.d.cts +11 -0
- package/dist/components/diff-renderer.d.ts +11 -0
- package/dist/components/diff-renderer.mjs +66 -0
- package/dist/components/index.cjs +2879 -1295
- package/dist/components/index.d.cts +9 -2
- package/dist/components/index.d.ts +9 -2
- package/dist/components/index.mjs +2842 -1277
- package/dist/components/katex-math.cjs +220 -0
- package/dist/components/katex-math.d.cts +10 -0
- package/dist/components/katex-math.d.ts +10 -0
- package/dist/components/katex-math.mjs +188 -0
- package/dist/contexts/math-tracker.cjs +240 -521
- package/dist/contexts/math-tracker.mjs +235 -513
- package/dist/index-Cmne1yUK.d.cts +723 -0
- package/dist/index-D7dyyu3q.d.ts +723 -0
- package/dist/index.cjs +13180 -5640
- package/dist/index.d.cts +72 -40
- package/dist/index.d.ts +72 -40
- package/dist/index.mjs +12587 -5038
- package/dist/math/display-wrapper.cjs +7 -15
- package/dist/math/display-wrapper.d.cts +2 -2
- package/dist/math/display-wrapper.d.ts +2 -2
- package/dist/math/display-wrapper.mjs +2 -6
- package/dist/node-views-Bpc6Gkgl.d.cts +13 -0
- package/dist/node-views-DpnYdaAS.d.ts +13 -0
- package/dist/renderer/hooks.cjs +35 -20
- package/dist/renderer/hooks.d.cts +22 -1
- package/dist/renderer/hooks.d.ts +22 -1
- package/dist/renderer/hooks.mjs +27 -12
- package/dist/renderer/patch-batching.cjs +7 -8
- package/dist/renderer/patch-batching.d.cts +1 -1
- package/dist/renderer/patch-batching.d.ts +1 -1
- package/dist/renderer/patch-batching.mjs +2 -1
- package/dist/renderer/patch-coalescing.cjs +5 -8
- package/dist/renderer/patch-commit-scheduler.cjs +160 -326
- package/dist/renderer/patch-commit-scheduler.d.cts +28 -4
- package/dist/renderer/patch-commit-scheduler.d.ts +28 -4
- package/dist/renderer/patch-commit-scheduler.mjs +157 -318
- package/dist/renderer/store.cjs +3847 -1099
- package/dist/renderer/store.d.cts +111 -3
- package/dist/renderer/store.d.ts +111 -3
- package/dist/renderer/store.mjs +3847 -1085
- package/dist/renderer/virtualized-code.cjs +415 -71
- package/dist/renderer/virtualized-code.d.cts +24 -23
- package/dist/renderer/virtualized-code.d.ts +24 -23
- package/dist/renderer/virtualized-code.mjs +409 -62
- package/dist/renderer.cjs +10939 -3974
- package/dist/renderer.d.cts +100 -29
- package/dist/renderer.d.ts +100 -29
- package/dist/renderer.mjs +10711 -3697
- package/dist/server-regions-CPLHGli0.d.cts +32 -0
- package/dist/server-regions-mUhyoKi7.d.ts +32 -0
- package/dist/server.cjs +10286 -3365
- package/dist/server.d.cts +17 -247
- package/dist/server.d.ts +17 -247
- package/dist/server.mjs +10565 -3619
- package/dist/{streaming-markdown-DOc2uLiP.d.ts → streaming-markdown-yELbJxyH.d.ts} +51 -55
- package/dist/{streaming-markdown-D-JcByd9.d.cts → streaming-markdown-zVwFuaJG.d.cts} +51 -55
- package/dist/streaming-markdown.cjs +11828 -4879
- package/dist/streaming-markdown.d.cts +9 -2
- package/dist/streaming-markdown.d.ts +9 -2
- package/dist/streaming-markdown.mjs +11820 -4821
- package/dist/trusted-mdx/index.cjs +833 -0
- package/dist/trusted-mdx/index.d.cts +11 -0
- package/dist/trusted-mdx/index.d.ts +11 -0
- package/dist/trusted-mdx/index.mjs +807 -0
- package/dist/trusted-mdx/native-children-slot.cjs +45 -0
- package/dist/trusted-mdx/native-children-slot.d.cts +11 -0
- package/dist/trusted-mdx/native-children-slot.d.ts +11 -0
- package/dist/trusted-mdx/native-children-slot.mjs +24 -0
- package/dist/trusted-mdx/server.cjs +752 -0
- package/dist/trusted-mdx/server.d.cts +24 -0
- package/dist/trusted-mdx/server.d.ts +24 -0
- package/dist/trusted-mdx/server.mjs +724 -0
- package/dist/trusted-mdx-contract-ElKpkDI5.d.cts +113 -0
- package/dist/trusted-mdx-contract-ElKpkDI5.d.ts +113 -0
- package/dist/utils/inline-html.cjs +246 -147
- package/dist/utils/inline-html.d.cts +39 -6
- package/dist/utils/inline-html.d.ts +39 -6
- package/dist/utils/inline-html.mjs +237 -134
- package/package.json +52 -18
- package/shadcn/README.md +28 -0
- package/shadcn/bottom-stick-scroll-area.tsx +358 -0
- package/dist/index-D2MYusAM.d.cts +0 -248
- package/dist/index-D2MYusAM.d.ts +0 -248
- package/dist/mdx-client.cjs +0 -408
- package/dist/mdx-client.d.cts +0 -113
- package/dist/mdx-client.d.ts +0 -113
- package/dist/mdx-client.mjs +0 -369
- package/dist/mdx-coordinator.cjs +0 -563
- package/dist/mdx-coordinator.d.cts +0 -21
- package/dist/mdx-coordinator.d.ts +0 -21
- package/dist/mdx-coordinator.mjs +0 -529
- package/dist/renderer/node-views.cjs +0 -1642
- package/dist/renderer/node-views.d.cts +0 -12
- package/dist/renderer/node-views.d.ts +0 -12
- 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
|
-
|
|
6
|
-
|
|
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
|
-
|
|
9
|
-
const match = BACKTICK_CONTENT.exec(trimmed);
|
|
102
|
+
let trimmed = descriptor.rawInner.trim(), match = BACKTICK_CONTENT.exec(trimmed);
|
|
10
103
|
if (match) {
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
23
|
-
if (!descriptor)
|
|
111
|
+
let descriptor = parseInlineHtmlDescriptor(raw, sanitized);
|
|
112
|
+
if (!descriptor)
|
|
24
113
|
return null;
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
40
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
68
|
-
|
|
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
|
-
|
|
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
|
-
|
|
164
|
+
let rawMatch = normalizedRaw.match(/^<([A-Za-z][\w:-]*)(\s[^>]*)?>([\s\S]*)<\/\1>$/);
|
|
82
165
|
rawInner = rawMatch ? rawMatch[3] ?? "" : normalizedRaw;
|
|
83
166
|
}
|
|
84
|
-
|
|
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
|
-
|
|
179
|
+
let attrs = {};
|
|
97
180
|
if (!input) return attrs;
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
248
|
+
let style = {};
|
|
129
249
|
if (!value) return style;
|
|
130
|
-
|
|
131
|
-
for (
|
|
250
|
+
let pairs = value.split(";");
|
|
251
|
+
for (let pair of pairs) {
|
|
132
252
|
if (!pair.trim()) continue;
|
|
133
|
-
|
|
134
|
-
if (
|
|
135
|
-
|
|
136
|
-
|
|
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
|
-
|
|
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
|
-
|
|
271
|
+
let withoutTags = value.replace(/<\/?[^>]+>/g, "");
|
|
150
272
|
return decodeHtmlEntities(withoutTags);
|
|
151
273
|
}
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
|
|
158
|
-
|
|
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
|
-
|
|
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
|
-
|
|
171
|
-
|
|
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
|
-
|
|
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
|
-
|
|
303
|
+
let isLiteral = parentIsLiteral || (literalTags ? literalTags.has(tagName) : !1), children = [];
|
|
203
304
|
element.childNodes.forEach((child, index) => {
|
|
204
|
-
|
|
205
|
-
|
|
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
|
-
|
|
210
|
-
|
|
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
|
-
|
|
216
|
-
for (
|
|
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.
|
|
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
|
|
36
|
-
"import": "./dist/renderer
|
|
37
|
-
"require": "./dist/renderer
|
|
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.
|
|
94
|
-
"@stream-mdx/
|
|
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
|
}
|
package/shadcn/README.md
ADDED
|
@@ -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`.
|