safe-mdx 1.3.2 → 1.3.6

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 (77) hide show
  1. package/README.md +14 -14
  2. package/dist/assets/HtmlToJsxConverter-Ds0bTjpw.js +24 -0
  3. package/dist/assets/_commonjsHelpers-CqkleIqs.js +1 -0
  4. package/dist/assets/index-B5fPOjPt.css +1 -0
  5. package/dist/assets/index-B7ATSoRE.js +9 -0
  6. package/dist/assets/index-BwZ2FTRd.js +146 -0
  7. package/dist/assets/index-R1UqLMGJ.js +1 -0
  8. package/dist/assets/index-c0qeY2gs.js +9 -0
  9. package/dist/assets/jsx-runtime-BhZZLbvw.js +9 -0
  10. package/dist/assets/jsx-runtime-NArryeSM.js +1 -0
  11. package/dist/assets/react-Ca6JzGpx.js +1 -0
  12. package/dist/assets/react-dom-BYRHYqYl.js +1 -0
  13. package/dist/html/attributes.d.ts +19 -0
  14. package/dist/html/attributes.d.ts.map +1 -0
  15. package/dist/html/attributes.js +289 -0
  16. package/dist/html/attributes.js.map +1 -0
  17. package/dist/html/convert-attributes.d.ts +6 -0
  18. package/dist/html/convert-attributes.d.ts.map +1 -0
  19. package/dist/html/convert-attributes.js +43 -0
  20. package/dist/html/convert-attributes.js.map +1 -0
  21. package/dist/html/domparser-browser.d.ts +4 -0
  22. package/dist/html/domparser-browser.d.ts.map +1 -0
  23. package/dist/html/domparser-browser.js +7 -0
  24. package/dist/html/domparser-browser.js.map +1 -0
  25. package/dist/html/domparser.d.ts +2 -0
  26. package/dist/html/domparser.d.ts.map +1 -0
  27. package/dist/html/domparser.js +5 -0
  28. package/dist/html/domparser.js.map +1 -0
  29. package/dist/html/html-to-mdx-ast.d.ts +25 -0
  30. package/dist/html/html-to-mdx-ast.d.ts.map +1 -0
  31. package/dist/html/html-to-mdx-ast.js +247 -0
  32. package/dist/html/html-to-mdx-ast.js.map +1 -0
  33. package/dist/html/html-to-mdx-ast.test.d.ts +2 -0
  34. package/dist/html/html-to-mdx-ast.test.d.ts.map +1 -0
  35. package/dist/html/html-to-mdx-ast.test.js +411 -0
  36. package/dist/html/html-to-mdx-ast.test.js.map +1 -0
  37. package/dist/html/remark-mdx-jsx-normalize.d.ts +10 -0
  38. package/dist/html/remark-mdx-jsx-normalize.d.ts.map +1 -0
  39. package/dist/html/remark-mdx-jsx-normalize.js +194 -0
  40. package/dist/html/remark-mdx-jsx-normalize.js.map +1 -0
  41. package/dist/html/valid-html-elements.d.ts +10 -0
  42. package/dist/html/valid-html-elements.d.ts.map +1 -0
  43. package/dist/html/valid-html-elements.js +50 -0
  44. package/dist/html/valid-html-elements.js.map +1 -0
  45. package/dist/index.html +19 -0
  46. package/dist/parse.d.ts +2 -0
  47. package/dist/parse.d.ts.map +1 -1
  48. package/dist/parse.js +2 -0
  49. package/dist/parse.js.map +1 -1
  50. package/dist/safe-mdx.d.ts +2 -2
  51. package/dist/safe-mdx.d.ts.map +1 -1
  52. package/dist/safe-mdx.js +24 -76
  53. package/dist/safe-mdx.js.map +1 -1
  54. package/dist/safe-mdx.test.js +161 -8
  55. package/dist/safe-mdx.test.js.map +1 -1
  56. package/package.json +28 -6
  57. package/src/html/README +17 -0
  58. package/src/html/attributes.ts +297 -0
  59. package/src/html/convert-attributes.ts +59 -0
  60. package/src/html/domparser-browser.ts +6 -0
  61. package/src/html/domparser.ts +5 -0
  62. package/src/html/html-to-mdx-ast.test.ts +459 -0
  63. package/src/html/html-to-mdx-ast.ts +327 -0
  64. package/src/html/remark-mdx-jsx-normalize.ts +209 -0
  65. package/src/html/valid-html-elements.ts +65 -0
  66. package/src/parse.ts +3 -0
  67. package/src/safe-mdx.test.tsx +178 -12
  68. package/src/safe-mdx.tsx +25 -84
  69. package/dist/HtmlToJsxConverter.d.ts +0 -10
  70. package/dist/HtmlToJsxConverter.d.ts.map +0 -1
  71. package/dist/HtmlToJsxConverter.js +0 -22
  72. package/dist/HtmlToJsxConverter.js.map +0 -1
  73. package/dist/plugins.d.ts +0 -12
  74. package/dist/plugins.d.ts.map +0 -1
  75. package/dist/plugins.js +0 -68
  76. package/dist/plugins.js.map +0 -1
  77. package/src/HtmlToJsxConverter.tsx +0 -37
@@ -0,0 +1,327 @@
1
+ import type { Root, RootContent, Text as MdastText } from 'mdast'
2
+ import type {
3
+ MdxJsxAttribute,
4
+ MdxJsxAttributeValueExpression,
5
+ MdxJsxTextElement,
6
+ } from 'mdast-util-mdx-jsx'
7
+ import type { Processor } from 'unified'
8
+ import { unified } from 'unified'
9
+ import { convertAttributeNameToJSX } from './convert-attributes.js'
10
+ import { parseHTML } from './domparser.js'
11
+ import { remarkMdxJsxNormalize } from './remark-mdx-jsx-normalize.js'
12
+
13
+ // Re-export the normalize plugin
14
+ export { remarkMdxJsxNormalize }
15
+
16
+ // Type for converting tag names
17
+ export type ConvertTagName = (args: { tagName: string }) => string
18
+
19
+ // Type for converting text to mdast nodes - now returns AST nodes directly
20
+ export type TextToMdast = (args: {
21
+ text: string
22
+ }) => RootContent | RootContent[]
23
+
24
+ // Type for converting attribute values
25
+ export type ConvertAttributeValue = (args: {
26
+ name: string
27
+ value: string
28
+ tagName: string
29
+ }) => string
30
+
31
+ // Options for parsing HTML to MDX AST
32
+ export interface ParseHtmlToMdxAstOptions {
33
+ html: string
34
+ parentType?: string
35
+ onError?: (error: unknown, text: string) => void
36
+ convertTagName?: ConvertTagName
37
+ textToMdast?: TextToMdast
38
+ convertAttributeValue?: ConvertAttributeValue
39
+ }
40
+
41
+ // Type guard functions for DOM nodes
42
+ function isCommentNode(node: Node): node is Comment {
43
+ return node.nodeType === 8 // Node.COMMENT_NODE
44
+ }
45
+
46
+ function isTextNode(node: Node): node is Text {
47
+ return node.nodeType === 3 // Node.TEXT_NODE
48
+ }
49
+
50
+ function isElementNode(node: Node): node is Element {
51
+ return node.nodeType === 1 // Node.ELEMENT_NODE
52
+ }
53
+
54
+ // Default tag name converter (no transformation)
55
+ function defaultConvertTagName({ tagName }: { tagName: string }): string {
56
+ return tagName.toLowerCase()
57
+ }
58
+
59
+ // Default attribute value converter (no transformation)
60
+ function defaultConvertAttributeValue({
61
+ value,
62
+ }: {
63
+ name: string
64
+ value: string
65
+ tagName: string
66
+ }): string {
67
+ return value
68
+ }
69
+
70
+ // Convert HTML attribute to MDX JSX attribute
71
+ function convertAttribute(
72
+ attr: Attr,
73
+ tagName: string,
74
+ options?: ParseHtmlToMdxAstOptions,
75
+ ): MdxJsxAttribute {
76
+ let jsxName = convertAttributeNameToJSX(attr.name)
77
+
78
+ // Apply attribute value transformation
79
+ const convertAttrValue =
80
+ options?.convertAttributeValue || defaultConvertAttributeValue
81
+ let value = convertAttrValue({
82
+ name: attr.name,
83
+ value: attr.value,
84
+ tagName,
85
+ })
86
+
87
+ // Handle boolean attributes
88
+ if (value === '' || value === attr.name) {
89
+ return {
90
+ type: 'mdxJsxAttribute',
91
+ name: jsxName,
92
+ value: null, // boolean true
93
+ }
94
+ }
95
+
96
+ // Handle special number attributes
97
+ const numberAttrs = [
98
+ 'tabIndex',
99
+ 'cols',
100
+ 'rows',
101
+ 'size',
102
+ 'span',
103
+ 'colSpan',
104
+ 'rowSpan',
105
+ 'border',
106
+ ]
107
+ if (numberAttrs.includes(jsxName) && value && !isNaN(Number(value))) {
108
+ return {
109
+ type: 'mdxJsxAttribute',
110
+ name: jsxName,
111
+ value: {
112
+ type: 'mdxJsxAttributeValueExpression',
113
+ value: value,
114
+ data: {
115
+ estree: {
116
+ type: 'Program',
117
+ sourceType: 'module',
118
+ body: [
119
+ {
120
+ type: 'ExpressionStatement',
121
+ expression: {
122
+ type: 'Literal',
123
+ value: Number(value),
124
+ },
125
+ },
126
+ ],
127
+ },
128
+ },
129
+ } satisfies MdxJsxAttributeValueExpression,
130
+ }
131
+ }
132
+
133
+ // Handle style attribute - for now keep as string
134
+ // if (jsxName === 'style' && value.includes(':')) {
135
+ // // Could enhance to parse CSS to object
136
+ // return {
137
+ // type: 'mdxJsxAttribute',
138
+ // name: jsxName,
139
+ // value: {
140
+ // type: 'mdxJsxAttributeValueExpression',
141
+ // value: `{${JSON.stringify(parseStyleString(value))}}`,
142
+ // data: {
143
+ // estree: parseExpression(JSON.stringify(parseStyleString(value))),
144
+ // },
145
+ // },
146
+ // }
147
+ // }
148
+
149
+ // String value
150
+ return {
151
+ type: 'mdxJsxAttribute',
152
+ name: jsxName,
153
+ value: value,
154
+ }
155
+ }
156
+
157
+ // Convert DOM node to MDX AST nodes - always returns an array
158
+ function htmlNodeToMdxAst(
159
+ node: Node,
160
+ options?: ParseHtmlToMdxAstOptions,
161
+ ): RootContent[] {
162
+ if (isCommentNode(node)) {
163
+ // Convert comments to MDX JSX expression with comment
164
+ // For now, return empty array
165
+ // return [{
166
+ // type: 'html',
167
+ // value: `<!-- ${node.data} -->`
168
+ // }] as Html[]
169
+ return []
170
+ }
171
+
172
+ if (isTextNode(node)) {
173
+ const textValue = node.textContent || ''
174
+
175
+ // If we have a textToMdast converter, use it
176
+ if (options?.textToMdast) {
177
+ try {
178
+ const result = options.textToMdast({ text: textValue })
179
+ return Array.isArray(result) ? result : [result]
180
+ } catch (error) {
181
+ // Call onError callback if provided, otherwise log
182
+ if (options.onError) {
183
+ options.onError(error, textValue)
184
+ } else {
185
+ console.error('Failed to convert text to mdast:', error)
186
+ console.error('Text content:', textValue)
187
+ }
188
+ // Fallback to simple text node
189
+ return [
190
+ {
191
+ type: 'text',
192
+ value: textValue,
193
+ } satisfies MdastText,
194
+ ]
195
+ }
196
+ }
197
+
198
+ // Default: return simple text node
199
+ return [
200
+ {
201
+ type: 'text',
202
+ value: textValue,
203
+ } satisfies MdastText,
204
+ ]
205
+ }
206
+
207
+ if (!isElementNode(node)) {
208
+ return []
209
+ }
210
+
211
+ const convertTagNameFn = options?.convertTagName || defaultConvertTagName
212
+ // Use localName which is always lowercase in both browser and linkedom
213
+ const componentName = convertTagNameFn({ tagName: node.localName })
214
+
215
+ // If convertTagName returns empty string, skip this element and only return its children
216
+ if (componentName === '') {
217
+ // Process children but skip the element wrapper
218
+ const children: RootContent[] = []
219
+ for (const child of Array.from(node.childNodes)) {
220
+ children.push(...htmlNodeToMdxAst(child, options))
221
+ }
222
+ return children
223
+ }
224
+
225
+ // Convert attributes
226
+ const attributes: MdxJsxAttribute[] = []
227
+ for (const attr of Array.from(node.attributes)) {
228
+ attributes.push(convertAttribute(attr, node.tagName, options))
229
+ }
230
+
231
+ // Process children
232
+ const children: RootContent[] = []
233
+ for (const child of Array.from(node.childNodes)) {
234
+ children.push(...htmlNodeToMdxAst(child, options))
235
+ }
236
+
237
+ // Always create MdxJsxTextElement initially
238
+ // The conversion to MdxJsxFlowElement will be handled by a separate plugin
239
+ const element: MdxJsxTextElement = {
240
+ type: 'mdxJsxTextElement',
241
+ name: componentName,
242
+ attributes,
243
+ children: children as any,
244
+ }
245
+ return [element]
246
+ }
247
+
248
+ // Main function to parse HTML and return MDX AST - always returns an array
249
+ export function htmlToMdxAst(options: ParseHtmlToMdxAstOptions): RootContent[] {
250
+ // Parse HTML with linkedom
251
+ const { document } = parseHTML(options.html.trim())
252
+
253
+ // linkedom behavior:
254
+ // - If input is a fragment (like "<div>Hello</div>"), the content becomes direct children of document
255
+ // - If input has body tag, it creates proper body element
256
+ // - We need to handle both cases
257
+
258
+ // linkedom behavior:
259
+ // - When parsing fragments, content becomes direct children of document
260
+ // - Accessing document.body on fragments auto-creates HEAD and BODY as children
261
+ // - We must avoid accessing document.body to prevent this
262
+
263
+ // Just use document's direct children and filter for relevant nodes
264
+ const childNodes = Array.from(document.childNodes).filter(
265
+ (node) =>
266
+ node.nodeType === 1 || // Element nodes
267
+ node.nodeType === 3 || // Text nodes
268
+ node.nodeType === 8, // Comment nodes
269
+ )
270
+
271
+ let results: RootContent[] = []
272
+
273
+ for (const node of childNodes) {
274
+ results.push(...htmlNodeToMdxAst(node, options))
275
+ }
276
+
277
+ // Apply the normalize plugin if we have a parentType
278
+ if (options.parentType && results.length > 0) {
279
+ // Create a temporary AST node with the same parent type
280
+ const parentType = options.parentType
281
+ const tempRoot: Root = {
282
+ type: 'root',
283
+ children: results,
284
+ }
285
+
286
+ // If we have a specific parent type, wrap the content in that parent
287
+ // to provide proper context for the normalize plugin
288
+ let astToProcess: Root
289
+ if (parentType !== 'root') {
290
+ // Create a parent node of the specified type with our content as children
291
+ const parentNode: any = {
292
+ type: parentType,
293
+ children: tempRoot.children,
294
+ }
295
+ astToProcess = {
296
+ type: 'root',
297
+ children: [parentNode],
298
+ }
299
+ } else {
300
+ astToProcess = tempRoot
301
+ }
302
+
303
+ // Create a simple processor and run the normalize plugin
304
+ const processor = unified().use(remarkMdxJsxNormalize)
305
+ processor.runSync(astToProcess)
306
+
307
+ // Extract the result back
308
+ if (parentType !== 'root') {
309
+ // Get the children from the parent node we created
310
+ const processedParent = astToProcess.children[0] as any
311
+ results = processedParent.children as RootContent[]
312
+ } else {
313
+ // Get children directly from root
314
+ results = astToProcess.children
315
+ }
316
+ }
317
+
318
+ return results
319
+ }
320
+
321
+ // Export a wrapper that always returns an array for consistency
322
+ // Note: htmlToMdxAst now already returns an array, so this is just an alias
323
+ export function parseHtmlToMdxAst(
324
+ options: ParseHtmlToMdxAstOptions,
325
+ ): RootContent[] {
326
+ return htmlToMdxAst(options)
327
+ }
@@ -0,0 +1,209 @@
1
+ import type { Root, RootContent, PhrasingContent } from 'mdast'
2
+ import type { MdxJsxTextElement, MdxJsxFlowElement } from 'mdast-util-mdx-jsx'
3
+ import { visitParents } from 'unist-util-visit-parents'
4
+ import type { Node, Parent } from 'unist'
5
+
6
+ // Type definitions for MDX and MDAST content types
7
+ // type FlowContent = Blockquote | Code | Heading | Html | List | ThematicBreak | Content
8
+ // type PhrasingContent = Break | Emphasis | Html | Image | ImageReference | InlineCode | Link | LinkReference | Strong | Text
9
+ // type MdxJsxFlowContent = MdxJsxFlowElement | FlowContent
10
+ // type MdxJsxPhrasingContent = MdxJsxTextElement | PhrasingContent
11
+
12
+ /** Parents that require phrasing/inline children */
13
+ const PHRASE_CONTAINERS = new Set([
14
+ 'paragraph',
15
+ 'heading',
16
+ 'emphasis',
17
+ 'strong',
18
+ 'delete',
19
+ 'link',
20
+ 'linkReference',
21
+ 'tableCell',
22
+ 'mdxJsxTextElement', // MDX JSX text elements should contain phrasing
23
+ ])
24
+
25
+ /** HTML tags that require phrasing/inline children */
26
+ const PHRASE_HTML_CONTAINERS = new Set([
27
+ 'p',
28
+ 'h1',
29
+ 'h2',
30
+ 'h3',
31
+ 'h4',
32
+ 'h5',
33
+ 'h6',
34
+ 'em',
35
+ 'strong',
36
+ 'b',
37
+ 'i',
38
+ 'u',
39
+ 's',
40
+ 'del',
41
+ 'ins',
42
+ 'mark',
43
+ 'small',
44
+ 'sub',
45
+ 'sup',
46
+ 'a',
47
+ 'abbr',
48
+ 'cite',
49
+ 'code',
50
+ 'dfn',
51
+ 'kbd',
52
+ 'q',
53
+ 'samp',
54
+ 'span',
55
+ 'time',
56
+ 'var',
57
+ ])
58
+
59
+ /** Parents that accept/expect flow (block) content */
60
+ const FLOW_CONTAINERS = new Set([
61
+ 'root',
62
+ 'listItem',
63
+ 'blockquote',
64
+ 'footnoteDefinition',
65
+ 'mdxJsxFlowElement', // MDX JSX flow elements should contain flow
66
+ ])
67
+
68
+ /** Check if a node represents phrasing content */
69
+ function isPhrasing(node: Node): boolean {
70
+ const phrasingTypes = new Set([
71
+ 'text',
72
+ 'emphasis',
73
+ 'strong',
74
+ 'delete',
75
+ 'html',
76
+ 'image',
77
+ 'imageReference',
78
+ 'inlineCode',
79
+ 'link',
80
+ 'linkReference',
81
+ 'break',
82
+ 'mdxJsxTextElement',
83
+ ])
84
+ return phrasingTypes.has(node.type)
85
+ }
86
+
87
+ /** Tags that are typically block-level elements */
88
+ const blockLevelTags = new Set([
89
+ 'div',
90
+ 'p',
91
+ 'blockquote',
92
+ 'h1',
93
+ 'h2',
94
+ 'h3',
95
+ 'h4',
96
+ 'h5',
97
+ 'h6',
98
+ 'ul',
99
+ 'ol',
100
+ 'li',
101
+ 'pre',
102
+ 'hr',
103
+ 'table',
104
+ 'thead',
105
+ 'tbody',
106
+ 'tfoot',
107
+ 'tr',
108
+ 'th',
109
+ 'td',
110
+ 'section',
111
+ 'article',
112
+ 'aside',
113
+ 'nav',
114
+ 'header',
115
+ 'footer',
116
+ 'main',
117
+ 'figure',
118
+ 'figcaption',
119
+ // Notion-specific block elements
120
+ 'callout',
121
+ 'columns',
122
+ 'column',
123
+ 'page',
124
+ 'database',
125
+ 'data-source',
126
+ 'audio',
127
+ 'video',
128
+ 'file',
129
+ 'pdf',
130
+ 'embed',
131
+ 'synced_block',
132
+ 'synced_block_reference',
133
+ 'meeting-notes',
134
+ 'summary',
135
+ 'notes',
136
+ 'transcript',
137
+ 'table_of_contents',
138
+ 'unknown',
139
+ 'image', // Images can be block-level in Notion
140
+ ])
141
+
142
+ /**
143
+ * remark plugin: make mdxJsx* element kinds match their context.
144
+ * - Inside phrasing parents → mdxJsxTextElement
145
+ * - Inside flow parents → mdxJsxFlowElement
146
+ * - Elements with block-level tag names → mdxJsxFlowElement
147
+ * - Elements containing non-phrasing children → mdxJsxFlowElement
148
+ */
149
+ export function remarkMdxJsxNormalize() {
150
+ return function transform(tree: Root) {
151
+ visitParents(tree, isMdxJsx, (node, ancestors) => {
152
+ const element = node as MdxJsxTextElement | MdxJsxFlowElement
153
+ const parent = ancestors[ancestors.length - 1] as Parent | undefined
154
+ if (!parent) return
155
+
156
+ // Check if parent expects phrasing or flow content
157
+ let parentExpectsPhrasing = false
158
+ let parentExpectsFlow = false
159
+
160
+ if ((parent.type === 'mdxJsxFlowElement' || parent.type === 'mdxJsxTextElement') &&
161
+ (parent as any).name) {
162
+ // For MDX JSX elements, check the tag name
163
+ const parentTagName = (parent as any).name.toLowerCase()
164
+ parentExpectsPhrasing = PHRASE_HTML_CONTAINERS.has(parentTagName)
165
+ // If not phrasing and is a block-level tag, it expects flow
166
+ parentExpectsFlow = !parentExpectsPhrasing && blockLevelTags.has(parentTagName)
167
+ } else {
168
+ // For mdast nodes, check the type
169
+ parentExpectsPhrasing = PHRASE_CONTAINERS.has(parent.type)
170
+ parentExpectsFlow = FLOW_CONTAINERS.has(parent.type)
171
+ }
172
+
173
+ // Check element properties
174
+ const hasBlockTag = element.name
175
+ ? blockLevelTags.has(element.name.toLowerCase())
176
+ : false
177
+ const children = (element.children || []) as RootContent[]
178
+ const containsNonPhrasing = children.some((c) => !isPhrasing(c))
179
+
180
+ // Determine desired type
181
+ let desired: 'mdxJsxTextElement' | 'mdxJsxFlowElement' =
182
+ element.type
183
+
184
+ // Priority rules:
185
+ // 1. If it has a block-level tag name, it should be flow
186
+ // 2. If it contains non-phrasing children, it should be flow
187
+ // 3. Otherwise, match parent context
188
+ if (hasBlockTag || containsNonPhrasing) {
189
+ desired = 'mdxJsxFlowElement'
190
+ } else if (parentExpectsPhrasing) {
191
+ desired = 'mdxJsxTextElement'
192
+ } else if (parentExpectsFlow) {
193
+ desired = 'mdxJsxFlowElement'
194
+ }
195
+
196
+ // Apply the change if needed
197
+ if (element.type !== desired) {
198
+ element.type = desired
199
+ }
200
+ })
201
+ }
202
+ }
203
+
204
+ /** Check if a node is an MDX JSX element */
205
+ function isMdxJsx(node: Node): boolean {
206
+ return (
207
+ node.type === 'mdxJsxTextElement' || node.type === 'mdxJsxFlowElement'
208
+ )
209
+ }
@@ -0,0 +1,65 @@
1
+ // List of valid HTML elements that should be preserved
2
+ // All other elements will be filtered out (return empty string)
3
+ export const validHtmlElements = new Set([
4
+ // Document metadata
5
+ 'base', 'head', 'link', 'meta', 'style', 'title',
6
+
7
+ // Content sectioning
8
+ 'address', 'article', 'aside', 'footer', 'header', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
9
+ 'main', 'nav', 'section',
10
+
11
+ // Text content
12
+ 'blockquote', 'dd', 'div', 'dl', 'dt', 'figcaption', 'figure', 'hr', 'li', 'ol', 'p', 'pre', 'ul',
13
+
14
+ // Inline text semantics
15
+ 'a', 'abbr', 'b', 'bdi', 'bdo', 'br', 'cite', 'code', 'data', 'dfn', 'em', 'i', 'kbd',
16
+ 'mark', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'small', 'span', 'strong', 'sub', 'sup',
17
+ 'time', 'u', 'var', 'wbr',
18
+
19
+ // Image and multimedia
20
+ 'area', 'audio', 'img', 'map', 'track', 'video',
21
+
22
+ // Embedded content
23
+ 'embed', 'iframe', 'object', 'param', 'picture', 'portal', 'source',
24
+
25
+ // SVG and MathML
26
+ 'svg', 'math', 'path', // Added 'path' from nativeTags
27
+
28
+ // Scripting
29
+ 'canvas', 'noscript', 'script',
30
+
31
+ // Demarcating edits
32
+ 'del', 'ins',
33
+
34
+ // Table content
35
+ 'caption', 'col', 'colgroup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'tr',
36
+
37
+ // Forms
38
+ 'button', 'datalist', 'fieldset', 'form', 'input', 'label', 'legend', 'meter', 'optgroup',
39
+ 'option', 'output', 'progress', 'select', 'textarea',
40
+
41
+ // Interactive elements
42
+ 'details', 'dialog', 'menu', 'summary',
43
+
44
+ // Web Components
45
+ 'slot', 'template',
46
+ ])
47
+
48
+ // Export as an array for backward compatibility with nativeTags
49
+ export const nativeTags = Array.from(validHtmlElements) as readonly string[]
50
+
51
+ /**
52
+ * Convert HTML tag name to JSX component name
53
+ * Returns empty string if the tag is not a valid HTML element
54
+ */
55
+ export function htmlTagNameConverter({ tagName }: { tagName: string }): string {
56
+ const lowerTag = tagName.toLowerCase()
57
+
58
+ // Check if it's a valid HTML element
59
+ if (validHtmlElements.has(lowerTag)) {
60
+ return lowerTag
61
+ }
62
+
63
+ // Return empty string for non-HTML elements
64
+ return ''
65
+ }
package/src/parse.ts CHANGED
@@ -5,6 +5,9 @@ import { Root, RootContent } from 'mdast'
5
5
  import { remark } from 'remark'
6
6
  import remarkGfm from 'remark-gfm'
7
7
  import remarkMdx from 'remark-mdx'
8
+ import { parseHtmlToMdxAst, remarkMdxJsxNormalize } from './html/html-to-mdx-ast.js'
9
+
10
+ export { parseHtmlToMdxAst, remarkMdxJsxNormalize }
8
11
 
9
12
  export function mdxParse(code: string) {
10
13
  const file = mdxProcessor.processSync(code)