@neeleshyadav/react-native-html-renderer 1.1.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 (155) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +407 -0
  3. package/lib/module/HtmlRenderer.js +183 -0
  4. package/lib/module/HtmlRenderer.js.map +1 -0
  5. package/lib/module/context/index.js +32 -0
  6. package/lib/module/context/index.js.map +1 -0
  7. package/lib/module/hooks/index.js +6 -0
  8. package/lib/module/hooks/index.js.map +1 -0
  9. package/lib/module/hooks/useContentWidth.js +12 -0
  10. package/lib/module/hooks/useContentWidth.js.map +1 -0
  11. package/lib/module/hooks/useHtmlParser.js +16 -0
  12. package/lib/module/hooks/useHtmlParser.js.map +1 -0
  13. package/lib/module/hooks/useTagStyle.js +26 -0
  14. package/lib/module/hooks/useTagStyle.js.map +1 -0
  15. package/lib/module/index.js +23 -0
  16. package/lib/module/index.js.map +1 -0
  17. package/lib/module/package.json +1 -0
  18. package/lib/module/parser/index.js +62 -0
  19. package/lib/module/parser/index.js.map +1 -0
  20. package/lib/module/renderer/ErrorBoundary.js +66 -0
  21. package/lib/module/renderer/ErrorBoundary.js.map +1 -0
  22. package/lib/module/renderer/NodeRenderer.js +279 -0
  23. package/lib/module/renderer/NodeRenderer.js.map +1 -0
  24. package/lib/module/renderer/index.js +5 -0
  25. package/lib/module/renderer/index.js.map +1 -0
  26. package/lib/module/renderer/tags/BlockTags.js +28 -0
  27. package/lib/module/renderer/tags/BlockTags.js.map +1 -0
  28. package/lib/module/renderer/tags/FormTags.js +129 -0
  29. package/lib/module/renderer/tags/FormTags.js.map +1 -0
  30. package/lib/module/renderer/tags/ImageTag.js +163 -0
  31. package/lib/module/renderer/tags/ImageTag.js.map +1 -0
  32. package/lib/module/renderer/tags/LinkTag.js +50 -0
  33. package/lib/module/renderer/tags/LinkTag.js.map +1 -0
  34. package/lib/module/renderer/tags/ListTags.js +96 -0
  35. package/lib/module/renderer/tags/ListTags.js.map +1 -0
  36. package/lib/module/renderer/tags/MediaTags.js +69 -0
  37. package/lib/module/renderer/tags/MediaTags.js.map +1 -0
  38. package/lib/module/renderer/tags/TableTags.js +48 -0
  39. package/lib/module/renderer/tags/TableTags.js.map +1 -0
  40. package/lib/module/renderer/tags/TextTags.js +87 -0
  41. package/lib/module/renderer/tags/TextTags.js.map +1 -0
  42. package/lib/module/renderer/tags/index.js +11 -0
  43. package/lib/module/renderer/tags/index.js.map +1 -0
  44. package/lib/module/styles/cssToRn.js +34 -0
  45. package/lib/module/styles/cssToRn.js.map +1 -0
  46. package/lib/module/styles/darkModeStyles.js +81 -0
  47. package/lib/module/styles/darkModeStyles.js.map +1 -0
  48. package/lib/module/styles/defaultStyles.js +218 -0
  49. package/lib/module/styles/defaultStyles.js.map +1 -0
  50. package/lib/module/styles/index.js +7 -0
  51. package/lib/module/styles/index.js.map +1 -0
  52. package/lib/module/styles/mergeStyles.js +47 -0
  53. package/lib/module/styles/mergeStyles.js.map +1 -0
  54. package/lib/module/types/index.js +4 -0
  55. package/lib/module/types/index.js.map +1 -0
  56. package/lib/module/utils/accessibility.js +108 -0
  57. package/lib/module/utils/accessibility.js.map +1 -0
  58. package/lib/module/utils/cache.js +69 -0
  59. package/lib/module/utils/cache.js.map +1 -0
  60. package/lib/module/utils/index.js +95 -0
  61. package/lib/module/utils/index.js.map +1 -0
  62. package/lib/module/utils/sanitize.js +102 -0
  63. package/lib/module/utils/sanitize.js.map +1 -0
  64. package/lib/typescript/package.json +1 -0
  65. package/lib/typescript/src/HtmlRenderer.d.ts +15 -0
  66. package/lib/typescript/src/HtmlRenderer.d.ts.map +1 -0
  67. package/lib/typescript/src/context/index.d.ts +5 -0
  68. package/lib/typescript/src/context/index.d.ts.map +1 -0
  69. package/lib/typescript/src/hooks/index.d.ts +4 -0
  70. package/lib/typescript/src/hooks/index.d.ts.map +1 -0
  71. package/lib/typescript/src/hooks/useContentWidth.d.ts +6 -0
  72. package/lib/typescript/src/hooks/useContentWidth.d.ts.map +1 -0
  73. package/lib/typescript/src/hooks/useHtmlParser.d.ts +11 -0
  74. package/lib/typescript/src/hooks/useHtmlParser.d.ts.map +1 -0
  75. package/lib/typescript/src/hooks/useTagStyle.d.ts +11 -0
  76. package/lib/typescript/src/hooks/useTagStyle.d.ts.map +1 -0
  77. package/lib/typescript/src/index.d.ts +9 -0
  78. package/lib/typescript/src/index.d.ts.map +1 -0
  79. package/lib/typescript/src/parser/index.d.ts +10 -0
  80. package/lib/typescript/src/parser/index.d.ts.map +1 -0
  81. package/lib/typescript/src/renderer/ErrorBoundary.d.ts +22 -0
  82. package/lib/typescript/src/renderer/ErrorBoundary.d.ts.map +1 -0
  83. package/lib/typescript/src/renderer/NodeRenderer.d.ts +7 -0
  84. package/lib/typescript/src/renderer/NodeRenderer.d.ts.map +1 -0
  85. package/lib/typescript/src/renderer/index.d.ts +3 -0
  86. package/lib/typescript/src/renderer/index.d.ts.map +1 -0
  87. package/lib/typescript/src/renderer/tags/BlockTags.d.ts +18 -0
  88. package/lib/typescript/src/renderer/tags/BlockTags.d.ts.map +1 -0
  89. package/lib/typescript/src/renderer/tags/FormTags.d.ts +16 -0
  90. package/lib/typescript/src/renderer/tags/FormTags.d.ts.map +1 -0
  91. package/lib/typescript/src/renderer/tags/ImageTag.d.ts +18 -0
  92. package/lib/typescript/src/renderer/tags/ImageTag.d.ts.map +1 -0
  93. package/lib/typescript/src/renderer/tags/LinkTag.d.ts +19 -0
  94. package/lib/typescript/src/renderer/tags/LinkTag.d.ts.map +1 -0
  95. package/lib/typescript/src/renderer/tags/ListTags.d.ts +15 -0
  96. package/lib/typescript/src/renderer/tags/ListTags.d.ts.map +1 -0
  97. package/lib/typescript/src/renderer/tags/MediaTags.d.ts +14 -0
  98. package/lib/typescript/src/renderer/tags/MediaTags.d.ts.map +1 -0
  99. package/lib/typescript/src/renderer/tags/TableTags.d.ts +15 -0
  100. package/lib/typescript/src/renderer/tags/TableTags.d.ts.map +1 -0
  101. package/lib/typescript/src/renderer/tags/TextTags.d.ts +22 -0
  102. package/lib/typescript/src/renderer/tags/TextTags.d.ts.map +1 -0
  103. package/lib/typescript/src/renderer/tags/index.d.ts +9 -0
  104. package/lib/typescript/src/renderer/tags/index.d.ts.map +1 -0
  105. package/lib/typescript/src/styles/cssToRn.d.ts +11 -0
  106. package/lib/typescript/src/styles/cssToRn.d.ts.map +1 -0
  107. package/lib/typescript/src/styles/darkModeStyles.d.ts +7 -0
  108. package/lib/typescript/src/styles/darkModeStyles.d.ts.map +1 -0
  109. package/lib/typescript/src/styles/defaultStyles.d.ts +8 -0
  110. package/lib/typescript/src/styles/defaultStyles.d.ts.map +1 -0
  111. package/lib/typescript/src/styles/index.d.ts +5 -0
  112. package/lib/typescript/src/styles/index.d.ts.map +1 -0
  113. package/lib/typescript/src/styles/mergeStyles.d.ts +10 -0
  114. package/lib/typescript/src/styles/mergeStyles.d.ts.map +1 -0
  115. package/lib/typescript/src/types/index.d.ts +158 -0
  116. package/lib/typescript/src/types/index.d.ts.map +1 -0
  117. package/lib/typescript/src/utils/accessibility.d.ts +32 -0
  118. package/lib/typescript/src/utils/accessibility.d.ts.map +1 -0
  119. package/lib/typescript/src/utils/cache.d.ts +24 -0
  120. package/lib/typescript/src/utils/cache.d.ts.map +1 -0
  121. package/lib/typescript/src/utils/index.d.ts +33 -0
  122. package/lib/typescript/src/utils/index.d.ts.map +1 -0
  123. package/lib/typescript/src/utils/sanitize.d.ts +11 -0
  124. package/lib/typescript/src/utils/sanitize.d.ts.map +1 -0
  125. package/package.json +171 -0
  126. package/src/HtmlRenderer.tsx +216 -0
  127. package/src/context/index.tsx +30 -0
  128. package/src/hooks/index.ts +3 -0
  129. package/src/hooks/useContentWidth.ts +9 -0
  130. package/src/hooks/useHtmlParser.ts +18 -0
  131. package/src/hooks/useTagStyle.ts +23 -0
  132. package/src/index.tsx +39 -0
  133. package/src/parser/index.ts +80 -0
  134. package/src/renderer/ErrorBoundary.tsx +80 -0
  135. package/src/renderer/NodeRenderer.tsx +345 -0
  136. package/src/renderer/index.tsx +2 -0
  137. package/src/renderer/tags/BlockTags.tsx +49 -0
  138. package/src/renderer/tags/FormTags.tsx +169 -0
  139. package/src/renderer/tags/ImageTag.tsx +215 -0
  140. package/src/renderer/tags/LinkTag.tsx +76 -0
  141. package/src/renderer/tags/ListTags.tsx +148 -0
  142. package/src/renderer/tags/MediaTags.tsx +81 -0
  143. package/src/renderer/tags/TableTags.tsx +94 -0
  144. package/src/renderer/tags/TextTags.tsx +139 -0
  145. package/src/renderer/tags/index.ts +8 -0
  146. package/src/styles/cssToRn.ts +45 -0
  147. package/src/styles/darkModeStyles.ts +80 -0
  148. package/src/styles/defaultStyles.ts +176 -0
  149. package/src/styles/index.ts +4 -0
  150. package/src/styles/mergeStyles.ts +59 -0
  151. package/src/types/index.ts +229 -0
  152. package/src/utils/accessibility.ts +132 -0
  153. package/src/utils/cache.ts +83 -0
  154. package/src/utils/index.ts +151 -0
  155. package/src/utils/sanitize.ts +149 -0
@@ -0,0 +1,139 @@
1
+ import { memo, type ReactNode } from 'react';
2
+ import { Text, type TextStyle } from 'react-native';
3
+ import type {
4
+ DOMNode,
5
+ DOMElement,
6
+ RNStyle,
7
+ HtmlRendererContextValue,
8
+ } from '../../types';
9
+ import { INLINE_TAGS, isInlineContent } from '../../utils';
10
+ import { getAccessibilityProps } from '../../utils/accessibility';
11
+
12
+ interface TextBlockProps {
13
+ tag: string;
14
+ node: DOMElement;
15
+ style: RNStyle;
16
+ children: DOMNode[];
17
+ nodeKey: string;
18
+ ctx: HtmlRendererContextValue;
19
+ renderNodes: (
20
+ nodes: DOMNode[],
21
+ ctx: HtmlRendererContextValue,
22
+ keyPrefix: string
23
+ ) => ReactNode[];
24
+ }
25
+
26
+ /**
27
+ * Renders a text-block tag (`<p>`, `<h1>`-`<h6>`, `<pre>`, `<label>`, etc.)
28
+ * as a single `<Text>` component.
29
+ */
30
+ export const TextBlock = memo(function TextBlock({
31
+ node,
32
+ style,
33
+ children,
34
+ nodeKey,
35
+ ctx,
36
+ renderNodes,
37
+ }: TextBlockProps) {
38
+ const content = isInlineContent(children)
39
+ ? renderInlineNodes(children, ctx, nodeKey, renderNodes)
40
+ : renderNodes(children, ctx, nodeKey);
41
+
42
+ const a11y = getAccessibilityProps(node);
43
+ const debugStyle: TextStyle | undefined = ctx.debug
44
+ ? { borderWidth: 1, borderColor: 'red' }
45
+ : undefined;
46
+
47
+ return (
48
+ <Text
49
+ key={nodeKey}
50
+ style={
51
+ debugStyle ? [style as TextStyle, debugStyle] : (style as TextStyle)
52
+ }
53
+ allowFontScaling={ctx.allowFontScaling}
54
+ maxFontSizeMultiplier={ctx.maxFontSizeMultiplier}
55
+ {...a11y}
56
+ {...ctx.defaultTextProps}
57
+ >
58
+ {content}
59
+ </Text>
60
+ );
61
+ });
62
+
63
+ /**
64
+ * Renders inline content (text nodes + inline tags) as nested `<Text>` elements.
65
+ */
66
+ export function renderInlineNodes(
67
+ nodes: DOMNode[],
68
+ ctx: HtmlRendererContextValue,
69
+ keyPrefix: string,
70
+ renderNodes: (
71
+ nodes: DOMNode[],
72
+ ctx: HtmlRendererContextValue,
73
+ keyPrefix: string
74
+ ) => ReactNode[]
75
+ ): ReactNode[] {
76
+ return nodes
77
+ .map((node, i) => {
78
+ const key = `${keyPrefix}_${i}`;
79
+
80
+ if (node.type === 'text') {
81
+ return node.data;
82
+ }
83
+
84
+ if (node.type === 'element' && INLINE_TAGS.has(node.tag)) {
85
+ if (node.tag === 'br') {
86
+ return '\n';
87
+ }
88
+
89
+ if (node.tag === 'a') {
90
+ const href = node.attributes.href ?? '';
91
+ const linkStyle: TextStyle = {
92
+ color: '#1a73e8',
93
+ textDecorationLine: 'underline' as const,
94
+ ...(ctx.tagsStyles.a as TextStyle),
95
+ };
96
+ return (
97
+ <Text
98
+ key={key}
99
+ style={linkStyle}
100
+ onPress={() => ctx.onLinkPress?.(href, node.attributes)}
101
+ accessibilityRole="link"
102
+ accessibilityHint={`Opens ${href}`}
103
+ accessibilityLabel={node.attributes['aria-label'] ?? undefined}
104
+ allowFontScaling={ctx.allowFontScaling}
105
+ maxFontSizeMultiplier={ctx.maxFontSizeMultiplier}
106
+ >
107
+ {renderInlineNodes(node.children, ctx, key, renderNodes)}
108
+ </Text>
109
+ );
110
+ }
111
+
112
+ const inlineStyle: RNStyle = {
113
+ ...(ctx.tagsStyles[node.tag] as TextStyle),
114
+ };
115
+
116
+ // Check aria-hidden
117
+ const ariaHidden = node.attributes['aria-hidden'] === 'true';
118
+
119
+ return (
120
+ <Text
121
+ key={key}
122
+ style={inlineStyle as TextStyle}
123
+ accessible={ariaHidden ? false : undefined}
124
+ importantForAccessibility={
125
+ ariaHidden ? 'no-hide-descendants' : undefined
126
+ }
127
+ allowFontScaling={ctx.allowFontScaling}
128
+ maxFontSizeMultiplier={ctx.maxFontSizeMultiplier}
129
+ >
130
+ {renderInlineNodes(node.children, ctx, key, renderNodes)}
131
+ </Text>
132
+ );
133
+ }
134
+
135
+ // Non-inline child — fall back
136
+ return renderNodes([node], ctx, key);
137
+ })
138
+ .filter((n) => n != null);
139
+ }
@@ -0,0 +1,8 @@
1
+ export { TextBlock, renderInlineNodes } from './TextTags';
2
+ export { ImageTag } from './ImageTag';
3
+ export { LinkTag } from './LinkTag';
4
+ export { ListTag } from './ListTags';
5
+ export { TableTag } from './TableTags';
6
+ export { BlockTag } from './BlockTags';
7
+ export { InputTag, TextareaTag, ButtonTag, SelectTag } from './FormTags';
8
+ export { VideoTag, AudioTag } from './MediaTags';
@@ -0,0 +1,45 @@
1
+ import transform from 'css-to-react-native';
2
+ import type { RNStyle } from '../types';
3
+
4
+ /**
5
+ * Parse an inline CSS `style` attribute string into a React Native style object.
6
+ *
7
+ * @param styleString - The raw CSS string (e.g. `"color: red; font-size: 18px"`).
8
+ * @param ignoredStyles - CSS property names to exclude.
9
+ * @param allowedStyles - If provided, only these CSS property names are kept.
10
+ * @returns A React Native compatible style object.
11
+ */
12
+ export function parseInlineStyle(
13
+ styleString: string,
14
+ ignoredStyles?: Set<string>,
15
+ allowedStyles?: Set<string> | null
16
+ ): RNStyle {
17
+ if (!styleString) return {};
18
+
19
+ try {
20
+ const pairs: [string, string][] = styleString
21
+ .split(';')
22
+ .map((s) => s.trim())
23
+ .filter(Boolean)
24
+ .map((declaration) => {
25
+ const colonIndex = declaration.indexOf(':');
26
+ if (colonIndex === -1) return null;
27
+ const prop = declaration.slice(0, colonIndex).trim();
28
+ const value = declaration.slice(colonIndex + 1).trim();
29
+ return [prop, value] as [string, string];
30
+ })
31
+ .filter((pair): pair is [string, string] => {
32
+ if (!pair) return false;
33
+ const [prop] = pair;
34
+ if (ignoredStyles?.has(prop)) return false;
35
+ if (allowedStyles && !allowedStyles.has(prop)) return false;
36
+ return true;
37
+ });
38
+
39
+ if (pairs.length === 0) return {};
40
+
41
+ return transform(pairs) as RNStyle;
42
+ } catch {
43
+ return {};
44
+ }
45
+ }
@@ -0,0 +1,80 @@
1
+ import type { TextStyle, ViewStyle } from 'react-native';
2
+ import type { TagsStyles } from '../types';
3
+
4
+ /**
5
+ * Default dark mode style overrides. These are merged on top of the
6
+ * default light-mode tag styles when the system color scheme is 'dark'.
7
+ */
8
+ export function getDefaultDarkModeStyles(): TagsStyles {
9
+ const textColor: TextStyle = { color: '#e0e0e0' };
10
+
11
+ return {
12
+ // Text elements — light text on dark background
13
+ p: textColor,
14
+ h1: textColor,
15
+ h2: textColor,
16
+ h3: textColor,
17
+ h4: textColor,
18
+ h5: textColor,
19
+ h6: textColor,
20
+ li: textColor,
21
+ dt: textColor,
22
+ dd: textColor,
23
+ label: textColor,
24
+
25
+ // Inline text
26
+ a: { color: '#8ab4f8', textDecorationLine: 'underline' } as TextStyle,
27
+ mark: { backgroundColor: '#5c5c00', color: '#e0e0e0' } as TextStyle,
28
+ code: {
29
+ backgroundColor: '#2d2d2d',
30
+ color: '#e0e0e0',
31
+ } as TextStyle,
32
+
33
+ // Preformatted
34
+ pre: {
35
+ backgroundColor: '#1e1e1e',
36
+ color: '#e0e0e0',
37
+ } as TextStyle,
38
+
39
+ // Blockquote
40
+ blockquote: {
41
+ borderLeftColor: '#555',
42
+ } as ViewStyle,
43
+
44
+ // Tables
45
+ th: {
46
+ ...textColor,
47
+ backgroundColor: '#2d2d2d',
48
+ borderColor: '#444',
49
+ } as TextStyle,
50
+ td: {
51
+ ...textColor,
52
+ borderColor: '#444',
53
+ } as TextStyle,
54
+
55
+ // Horizontal rule
56
+ hr: {
57
+ borderBottomColor: '#555',
58
+ } as ViewStyle,
59
+
60
+ // Forms (read-only)
61
+ input: {
62
+ ...textColor,
63
+ borderColor: '#555',
64
+ backgroundColor: '#2d2d2d',
65
+ } as TextStyle,
66
+ textarea: {
67
+ ...textColor,
68
+ borderColor: '#555',
69
+ backgroundColor: '#2d2d2d',
70
+ } as TextStyle,
71
+ button: {
72
+ backgroundColor: '#444',
73
+ } as ViewStyle,
74
+ select: {
75
+ ...textColor,
76
+ borderColor: '#555',
77
+ backgroundColor: '#2d2d2d',
78
+ } as TextStyle,
79
+ };
80
+ }
@@ -0,0 +1,176 @@
1
+ import type { TextStyle, ViewStyle } from 'react-native';
2
+ import type { TagsStyles } from '../types';
3
+
4
+ /**
5
+ * Returns the default styles for all supported HTML tags.
6
+ *
7
+ * @param emSize - Base em unit in pixels (used for heading sizes etc.).
8
+ */
9
+ export function getDefaultTagStyles(emSize: number): TagsStyles {
10
+ const base: TextStyle = { fontSize: emSize };
11
+
12
+ return {
13
+ // Block text
14
+ p: { ...base, marginVertical: 4 } as TextStyle,
15
+ h1: {
16
+ ...base,
17
+ fontSize: emSize * 2,
18
+ fontWeight: 'bold',
19
+ marginVertical: 10,
20
+ } as TextStyle,
21
+ h2: {
22
+ ...base,
23
+ fontSize: emSize * 1.5,
24
+ fontWeight: 'bold',
25
+ marginVertical: 9,
26
+ } as TextStyle,
27
+ h3: {
28
+ ...base,
29
+ fontSize: emSize * 1.17,
30
+ fontWeight: 'bold',
31
+ marginVertical: 8,
32
+ } as TextStyle,
33
+ h4: {
34
+ ...base,
35
+ fontWeight: 'bold',
36
+ marginVertical: 6,
37
+ } as TextStyle,
38
+ h5: {
39
+ ...base,
40
+ fontSize: emSize * 0.83,
41
+ fontWeight: 'bold',
42
+ marginVertical: 5,
43
+ } as TextStyle,
44
+ h6: {
45
+ ...base,
46
+ fontSize: emSize * 0.67,
47
+ fontWeight: 'bold',
48
+ marginVertical: 4,
49
+ } as TextStyle,
50
+
51
+ // Inline text
52
+ strong: { fontWeight: 'bold' } as TextStyle,
53
+ b: { fontWeight: 'bold' } as TextStyle,
54
+ em: { fontStyle: 'italic' } as TextStyle,
55
+ i: { fontStyle: 'italic' } as TextStyle,
56
+ u: { textDecorationLine: 'underline' } as TextStyle,
57
+ s: { textDecorationLine: 'line-through' } as TextStyle,
58
+ strike: { textDecorationLine: 'line-through' } as TextStyle,
59
+ del: { textDecorationLine: 'line-through' } as TextStyle,
60
+ ins: { textDecorationLine: 'underline' } as TextStyle,
61
+ mark: { backgroundColor: '#ff0' } as TextStyle,
62
+ small: { fontSize: emSize * 0.8 } as TextStyle,
63
+ sub: { fontSize: emSize * 0.75 } as TextStyle,
64
+ sup: { fontSize: emSize * 0.75 } as TextStyle,
65
+ code: {
66
+ fontFamily: 'monospace',
67
+ backgroundColor: '#f0f0f0',
68
+ paddingHorizontal: 3,
69
+ } as TextStyle,
70
+
71
+ // Links
72
+ a: {
73
+ color: '#1a73e8',
74
+ textDecorationLine: 'underline',
75
+ } as TextStyle,
76
+
77
+ // Lists
78
+ ul: { marginVertical: 4, paddingLeft: 20 } as ViewStyle,
79
+ ol: { marginVertical: 4, paddingLeft: 20 } as ViewStyle,
80
+ li: { ...base, marginVertical: 2 } as TextStyle,
81
+
82
+ // Separators
83
+ hr: {
84
+ marginVertical: 8,
85
+ } as ViewStyle,
86
+
87
+ // Preformatted
88
+ pre: {
89
+ ...base,
90
+ fontFamily: 'monospace',
91
+ backgroundColor: '#f5f5f5',
92
+ padding: 10,
93
+ borderRadius: 4,
94
+ marginVertical: 6,
95
+ } as TextStyle,
96
+
97
+ // Blockquote
98
+ blockquote: {
99
+ borderLeftWidth: 3,
100
+ borderLeftColor: '#ccc',
101
+ paddingLeft: 12,
102
+ marginVertical: 6,
103
+ marginLeft: 0,
104
+ } as ViewStyle,
105
+
106
+ // Tables
107
+ table: { marginVertical: 6 } as ViewStyle,
108
+ th: {
109
+ ...base,
110
+ fontWeight: 'bold',
111
+ padding: 6,
112
+ borderWidth: 1,
113
+ borderColor: '#ddd',
114
+ backgroundColor: '#f9f9f9',
115
+ } as TextStyle,
116
+ td: {
117
+ ...base,
118
+ padding: 6,
119
+ borderWidth: 1,
120
+ borderColor: '#ddd',
121
+ } as TextStyle,
122
+ tr: { flexDirection: 'row' } as ViewStyle,
123
+
124
+ // Block containers
125
+ div: {} as ViewStyle,
126
+ section: {} as ViewStyle,
127
+ article: {} as ViewStyle,
128
+ header: {} as ViewStyle,
129
+ footer: {} as ViewStyle,
130
+ main: {} as ViewStyle,
131
+ nav: {} as ViewStyle,
132
+ aside: {} as ViewStyle,
133
+
134
+ // Images
135
+ img: {} as ViewStyle,
136
+
137
+ // Forms (read-only)
138
+ input: {
139
+ borderWidth: 1,
140
+ borderColor: '#ccc',
141
+ borderRadius: 4,
142
+ padding: 6,
143
+ marginVertical: 2,
144
+ ...base,
145
+ } as TextStyle,
146
+ textarea: {
147
+ borderWidth: 1,
148
+ borderColor: '#ccc',
149
+ borderRadius: 4,
150
+ padding: 6,
151
+ marginVertical: 2,
152
+ minHeight: 60,
153
+ ...base,
154
+ } as TextStyle,
155
+ button: {
156
+ backgroundColor: '#e0e0e0',
157
+ borderRadius: 4,
158
+ padding: 8,
159
+ marginVertical: 2,
160
+ alignItems: 'center',
161
+ } as ViewStyle,
162
+ select: {
163
+ borderWidth: 1,
164
+ borderColor: '#ccc',
165
+ borderRadius: 4,
166
+ padding: 6,
167
+ marginVertical: 2,
168
+ ...base,
169
+ } as TextStyle,
170
+
171
+ // Definition lists
172
+ dl: { marginVertical: 4 } as ViewStyle,
173
+ dt: { ...base, fontWeight: 'bold', marginVertical: 2 } as TextStyle,
174
+ dd: { ...base, marginLeft: 20, marginVertical: 2 } as TextStyle,
175
+ };
176
+ }
@@ -0,0 +1,4 @@
1
+ export { parseInlineStyle } from './cssToRn';
2
+ export { getDefaultTagStyles } from './defaultStyles';
3
+ export { getDefaultDarkModeStyles } from './darkModeStyles';
4
+ export { mergeStylesForElement } from './mergeStyles';
@@ -0,0 +1,59 @@
1
+ import type {
2
+ RNStyle,
3
+ TagsStyles,
4
+ ClassesStyles,
5
+ IdsStyles,
6
+ DOMElement,
7
+ } from '../types';
8
+ import { parseInlineStyle } from './cssToRn';
9
+
10
+ /**
11
+ * Merge all style layers for a given DOM element in the correct cascade order:
12
+ *
13
+ * defaultTagStyle → tagsStyles → classesStyles → idsStyles → inline style
14
+ *
15
+ * Later layers override earlier ones.
16
+ */
17
+ export function mergeStylesForElement(
18
+ node: DOMElement,
19
+ defaultStyles: TagsStyles,
20
+ tagsStyles: TagsStyles,
21
+ classesStyles: ClassesStyles,
22
+ idsStyles: IdsStyles,
23
+ ignoredStyles?: Set<string>,
24
+ allowedStyles?: Set<string> | null
25
+ ): RNStyle {
26
+ // 1. Default tag style
27
+ const base: RNStyle = (defaultStyles[node.tag] as RNStyle) ?? {};
28
+
29
+ // 2. User tag override
30
+ const tagOverride: RNStyle = (tagsStyles[node.tag] as RNStyle) ?? {};
31
+
32
+ // 3. Class-based styles
33
+ let classStyle: RNStyle = {};
34
+ const className = node.attributes.class;
35
+ if (className) {
36
+ const classes = className.split(/\s+/).filter(Boolean);
37
+ for (const cls of classes) {
38
+ const s = classesStyles[cls];
39
+ if (s) classStyle = { ...classStyle, ...s } as RNStyle;
40
+ }
41
+ }
42
+
43
+ // 4. ID-based style
44
+ const id = node.attributes.id;
45
+ const idStyle: RNStyle = id ? ((idsStyles[id] as RNStyle) ?? {}) : {};
46
+
47
+ // 5. Inline style
48
+ const inlineStyle = node.attributes.style
49
+ ? parseInlineStyle(node.attributes.style, ignoredStyles, allowedStyles)
50
+ : {};
51
+
52
+ return {
53
+ ...base,
54
+ ...tagOverride,
55
+ ...classStyle,
56
+ ...idStyle,
57
+ ...inlineStyle,
58
+ } as RNStyle;
59
+ }