@nikpnevmatikos/html-renderer 0.1.0-alpha.2 → 0.1.0-alpha.4

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/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Html-Renderer contributors
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Html-Renderer contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,273 +1,282 @@
1
- # Html-Renderer
2
-
3
- A modern React Native HTML renderer, written in TypeScript with **zero native modules**. Built from scratch as a maintained alternative to the abandoned `react-native-render-html`.
4
-
5
- - **Zero native code** — works on iOS, Android, Web (via `react-native-web`), and Expo Go without a dev build.
6
- - **Fabric (new architecture) compatible** out of the box.
7
- - **Real CSS stylesheet support** a `stylesheet` prop that accepts actual CSS with selectors and specificity. (`react-native-render-html` doesn't do this.)
8
- - Transient render tree model: HTML → DOM → resolved render tree → `<Text>` / `<View>` / `<Image>`.
9
- - Full style inheritance, CSS cascade, and the box-model basics.
10
- - Extensible via custom renderers, custom element models, DOM transform hooks, and per-renderer config.
11
- - 100+ unit tests, typed end-to-end.
12
-
13
- ## Install
14
-
15
- ```bash
16
- npm install @nikpnevmatikos/html-renderer
17
- ```
18
-
19
- Peer dependencies: `react >= 18`, `react-native >= 0.73`.
20
-
21
- ## Quick start
22
-
23
- ```tsx
24
- import { HtmlRenderer } from '@nikpnevmatikos/html-renderer';
25
-
26
- export default function Screen() {
27
- return (
28
- <HtmlRenderer
29
- html={`<h1>Hello</h1><p>This is <strong>bold</strong> and <a href="https://x.dev">a link</a>.</p>`}
30
- />
31
- );
32
- }
33
- ```
34
-
35
- ## Props
36
-
37
- | Prop | Type | Description |
38
- |---|---|---|
39
- | `html` | `string` | The HTML source to render. |
40
- | `baseStyle` | `ResolvedStyle` | Root style inherited by all content. |
41
- | `stylesheet` | `string` | A CSS stylesheet with real selectors (type, class, id, descendant, child). |
42
- | `tagsStyles` | `Record<tag, StyleInput>` | Per-tag style override (`{ h1: {...} }`). |
43
- | `classesStyles` | `Record<class, StyleInput>` | Style by `class` attribute. |
44
- | `idsStyles` | `Record<id, StyleInput>` | Style by `id` attribute. |
45
- | `customRenderers` | `Record<tag, CustomRenderer>` | Replace or wrap the renderer for any tag. |
46
- | `customHTMLElementModels` | `Record<tag, HTMLElementModel>` | Define new tags with custom block/inline semantics and default styles. |
47
- | `renderersProps` | `Record<tag, Record<string, unknown>>` | Per-renderer config. Built-in consumers: `ol.startIndex`, `ul/ol.markerTextStyle`, `img.initialDimensions`. |
48
- | `contentWidth` | `number` | Max render width. Images wider than this scale down proportionally. |
49
- | `transformDom` | `(dom: DomNode[]) => DomNode[]` | Runs after parse, before build. Use for sanitization or tag rewrites. |
50
- | `onLinkPress` | `(href, attribs) => void` | Override the default `Linking.openURL` link handler. |
51
- | `ignoredDomTags` | `string[]` | Tags to drop during parse (subtree removed). |
52
- | `ignoredStyles` | `string[]` | CSS properties to drop. Accepts kebab-case (`background-color`) or camelCase (`backgroundColor`). |
53
- | `defaultTextProps` | `TextProps` | Spread onto every `<Text>`. |
54
- | `defaultViewProps` | `ViewProps` | Spread onto every `<View>`. |
55
- | `textSelectable` | `boolean` | Shortcut for `defaultTextProps.selectable = true`. |
56
-
57
- `StyleInput` is `ResolvedStyle | string` every style map accepts either an RN-style object *or* a CSS declarations string:
58
-
59
- ```tsx
60
- tagsStyles={{
61
- h1: { color: 'red', fontSize: 24 },
62
- h2: 'color: blue; font-size: 20px', // CSS string works too
63
- }}
64
- ```
65
-
66
- ## Supported tags
67
-
68
- **Block:** `p`, `div`, `h1–h6`, `ul`, `ol`, `li`, `pre`, `blockquote`, `hr`, `table`, `thead`, `tbody`, `tfoot`, `tr`, `th`, `td`, `caption`.
69
-
70
- **Inline:** `span`, `strong`/`b`, `em`/`i`, `u`, `s`/`del`/`strike`, `ins`, `mark`, `small`, `code`, `a`, `br`, `img`.
71
-
72
- ## Supported CSS
73
-
74
- - Typography: `color`, `font-size` (px), `font-family`, `font-weight`, `font-style`, `text-align`, `text-decoration`, `line-height` (px)
75
- - Box model: `margin` (shorthand + individual sides), `padding` (shorthand + individual sides), `background-color`
76
- - Colors: hex, `rgb()`, `rgba()`, `hsl()`, named (passed through to RN's color system)
77
- - Units: **`px` only** for now `em`/`rem`/`%` are not resolved yet
78
-
79
- ## Examples
80
-
81
- ### `stylesheet` — real CSS with selectors
82
-
83
- ```tsx
84
- const css = `
85
- article.card {
86
- background-color: #fafbfc;
87
- padding: 12px;
88
- }
89
- article.card h3 {
90
- color: #1a73e8;
91
- }
92
- .highlight {
93
- background-color: #fff3a3;
94
- }
95
- h1 > span {
96
- font-weight: bold;
97
- }
98
- `;
99
-
100
- <HtmlRenderer html={html} stylesheet={css} />;
101
- ```
102
-
103
- Supports: type (`h1`), class (`.foo`), id (`#bar`), universal (`*`), compound (`h1.big#hero`), descendant (`article span`), child (`h1 > span`), selector lists (`h1, h2`). Specificity and source order work per the CSS spec.
104
-
105
- Not supported: pseudo-classes, pseudo-elements, attribute selectors, sibling combinators (`+`, `~`), `@media` queries.
106
-
107
- ### Custom renderer
108
-
109
- ```tsx
110
- import { type CustomRenderer } from '@nikpnevmatikos/html-renderer';
111
-
112
- const customRenderers: Record<string, CustomRenderer> = {
113
- h1: (node, defaultRender) => (
114
- <View style={{ borderBottomWidth: 2, borderBottomColor: 'blue' }}>
115
- {defaultRender()}
116
- </View>
117
- ),
118
- };
119
-
120
- <HtmlRenderer html={html} customRenderers={customRenderers} />;
121
- ```
122
-
123
- ### Custom HTML element models
124
-
125
- Define your own tags that behave like real HTML:
126
-
127
- ```tsx
128
- import { type HTMLElementModel } from '@nikpnevmatikos/html-renderer';
129
-
130
- const customHTMLElementModels: Record<string, HTMLElementModel> = {
131
- 'my-card': {
132
- display: 'block',
133
- tagDefaultStyle: { backgroundColor: '#eef', padding: 12 },
134
- },
135
- 'x-spacer': {
136
- display: 'block',
137
- isVoid: true, // ignore any children
138
- tagDefaultStyle: { height: 20 },
139
- },
140
- };
141
-
142
- <HtmlRenderer
143
- html="<my-card>hello</my-card>"
144
- customHTMLElementModels={customHTMLElementModels}
145
- />;
146
- ```
147
-
148
- ### DOM transform hook
149
-
150
- ```tsx
151
- import { type TransformDom } from '@nikpnevmatikos/html-renderer';
152
-
153
- const sanitize: TransformDom = (dom) => walk(dom);
154
-
155
- function walk(nodes) {
156
- return nodes.map((n) => {
157
- if (n.type === 'element') {
158
- return { ...n, attribs: { ...n.attribs, onclick: '' }, children: walk(n.children) };
159
- }
160
- return n;
161
- });
162
- }
163
-
164
- <HtmlRenderer html={html} transformDom={sanitize} />;
165
- ```
166
-
167
- ### Link handling
168
-
169
- ```tsx
170
- import { type OnLinkPress } from '@nikpnevmatikos/html-renderer';
171
-
172
- const onLinkPress: OnLinkPress = (href, attribs) => {
173
- if (attribs.target === '_blank') {
174
- void Linking.openURL(href);
175
- } else {
176
- navigation.navigate('InAppBrowser', { url: href });
177
- }
178
- };
179
-
180
- <HtmlRenderer html={html} onLinkPress={onLinkPress} />;
181
- ```
182
-
183
- ### Auto-fit images
184
-
185
- ```tsx
186
- import { Dimensions } from 'react-native';
187
-
188
- const contentWidth = Dimensions.get('window').width - 32;
189
-
190
- <HtmlRenderer html={html} contentWidth={contentWidth} />;
191
- ```
192
-
193
- ### Renderers props (per-renderer config)
194
-
195
- ```tsx
196
- <HtmlRenderer
197
- html={html}
198
- renderersProps={{
199
- ol: { startIndex: 5, markerTextStyle: { color: '#888' } },
200
- ul: { markerTextStyle: { color: 'red' } },
201
- img: { initialDimensions: { width: 300, height: 200 } },
202
- }}
203
- />
204
- ```
205
-
206
- ## Cascade order
207
-
208
- From lowest to highest priority:
209
-
210
- ```
211
- 1. baseStyle (HtmlRenderer prop — root defaults)
212
- 2. Built-in tag defaults (h1 bold, strong bold, etc.)
213
- 3. stylesheet matches (by selector specificity + source order)
214
- 4. tagsStyles (per-tag programmatic override)
215
- 5. classesStyles (by matched class)
216
- 6. idsStyles (by matched id)
217
- 7. Inline style="..." (highest — HTML inline always wins)
218
- ```
219
-
220
- ## How it works
221
-
222
- ```
223
- HTML string
224
- └─ parseHtml (htmlparser2) → DOM tree
225
- └─ transformDom? (optional) → DOM tree
226
- └─ buildRenderTree → Render tree
227
- └─ resolveStyles (full cascade per element)
228
- └─ hoistBlocks (fragment inline-wrapping-block)
229
- └─ collapseWhitespace (CSS whitespace rules)
230
- └─ Renderer → <View> / <Text> / <Image>
231
- ```
232
-
233
- Styles resolve at build time into a single `ResolvedStyle` per element. At render time, `splitStyle` partitions each style into View-applicable and Text-applicable halves and applies them to the correct component.
234
-
235
- ## Current limitations
236
-
237
- Actively on the roadmap:
238
-
239
- - **No `rowspan`** on tables (colspan works).
240
- - **CSS units** beyond `px` — `em`, `rem`, `%` not resolved yet.
241
- - **Forms** — `<input>`, `<textarea>`, `<button>`, `<select>` not yet rendered (planned for core, pure-JS via RN's `TextInput` / `Pressable`).
242
- - **Stylesheet features** pseudo-classes (`:first-child`, `:nth-child`), attribute selectors (`[type="text"]`), and `@media` queries not yet supported.
243
- - **Advanced CSS** — transforms, opacity, individual `border-*` sides, `border-radius`, flex/grid display modes.
244
- - **Intrinsic table column widths** — columns render equal-width (`flex: 1`); `width` on `<col>` / cells is ignored.
245
-
246
- ## Plugin packages (planned)
247
-
248
- Features that need native dependencies ship as separate packages so the core stays zero-native-modules. Each uses the core's `customRenderers` and `customHTMLElementModels` APIs — no core changes needed to add them.
249
-
250
- | Tags | Planned package | Native peer dep |
251
- |---|---|---|
252
- | `<iframe>` | `@nikpnevmatikos/html-renderer-webview` | `react-native-webview` |
253
- | `<video>`, `<audio>` | `@nikpnevmatikos/html-renderer-video` | `expo-video` or `react-native-video` |
254
- | `<svg>` | `@nikpnevmatikos/html-renderer-svg` | `react-native-svg` |
255
-
256
- Until these ship, you can wire any of these tags yourself via `customRenderers` — the same API the plugins will use. See the "Custom renderer" and "Custom HTML element models" examples above.
257
-
258
- ## Development
259
-
260
- ```bash
261
- npm install # installs all workspace deps
262
- npm run dev # tsc --watch on core
263
- npm test # jest 100+ tests
264
- npm run typecheck # tsc --noEmit on core
265
- npm run build # build core to dist
266
-
267
- # live example app
268
- cd example && npm start
269
- ```
270
-
271
- ## License
272
-
273
- MIT see [LICENSE](./LICENSE).
1
+ # Html-Renderer
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@nikpnevmatikos/html-renderer?color=blue&label=npm)](https://www.npmjs.com/package/@nikpnevmatikos/html-renderer)
4
+ [![CI](https://github.com/NikPnevmatikos/Html-Renderer/actions/workflows/ci.yml/badge.svg)](https://github.com/NikPnevmatikos/Html-Renderer/actions/workflows/ci.yml)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
6
+
7
+ A modern React Native HTML renderer, written in TypeScript with **zero native modules**. Built from scratch as a maintained alternative to the abandoned `react-native-render-html`.
8
+
9
+ > **Status:** currently in alpha (`0.1.0-alpha.4`). Install with `@alpha` tag — see below.
10
+
11
+ - **Zero native code** — works on iOS, Android, Web (via `react-native-web`), and Expo Go without a dev build.
12
+ - **Fabric (new architecture) compatible** out of the box.
13
+ - **Real CSS stylesheet support** — a `stylesheet` prop that accepts actual CSS with selectors and specificity.
14
+ - Transient render tree model: HTML → DOM → resolved render tree → `<Text>` / `<View>` / `<Image>`.
15
+ - Full style inheritance, CSS cascade, and the box-model basics.
16
+ - Extensible via custom renderers, custom element models, DOM transform hooks, and per-renderer config.
17
+ - **Entity-encoded HTML** (`&lt;p&gt;hello&lt;/p&gt;` from CMS/API backends, even double-encoded) is auto-detected and rendered as HTML — no pre-decoding needed.
18
+ - 100+ unit tests, typed end-to-end.
19
+
20
+ ## Install
21
+
22
+ ```bash
23
+ npm install @nikpnevmatikos/html-renderer@alpha
24
+ ```
25
+
26
+ Peer dependencies: `react >= 18`, `react-native >= 0.73`.
27
+
28
+ > The `@alpha` tag is needed while the package is in prerelease. Once `0.1.0` stable is published, plain `npm install @nikpnevmatikos/html-renderer` will work.
29
+
30
+ ## Quick start
31
+
32
+ ```tsx
33
+ import { HtmlRenderer } from '@nikpnevmatikos/html-renderer';
34
+
35
+ export default function Screen() {
36
+ return (
37
+ <HtmlRenderer
38
+ html={`<h1>Hello</h1><p>This is <strong>bold</strong> and <a href="https://x.dev">a link</a>.</p>`}
39
+ />
40
+ );
41
+ }
42
+ ```
43
+
44
+ ## Props
45
+
46
+ | Prop | Type | Description |
47
+ |---|---|---|
48
+ | `html` | `string` | The HTML source to render. |
49
+ | `baseStyle` | `ResolvedStyle` | Root style inherited by all content. |
50
+ | `stylesheet` | `string` | A CSS stylesheet with real selectors (type, class, id, descendant, child). |
51
+ | `tagsStyles` | `Record<tag, StyleInput>` | Per-tag style override (`{ h1: {...} }`). |
52
+ | `classesStyles` | `Record<class, StyleInput>` | Style by `class` attribute. |
53
+ | `idsStyles` | `Record<id, StyleInput>` | Style by `id` attribute. |
54
+ | `customRenderers` | `Record<tag, CustomRenderer>` | Replace or wrap the renderer for any tag. |
55
+ | `customHTMLElementModels` | `Record<tag, HTMLElementModel>` | Define new tags with custom block/inline semantics and default styles. |
56
+ | `renderersProps` | `Record<tag, Record<string, unknown>>` | Per-renderer config. Built-in consumers: `ol.startIndex`, `ul/ol.markerTextStyle`, `img.initialDimensions`. |
57
+ | `contentWidth` | `number` | Max render width. Images wider than this scale down proportionally. |
58
+ | `transformDom` | `(dom: DomNode[]) => DomNode[]` | Runs after parse, before build. Use for sanitization or tag rewrites. |
59
+ | `onLinkPress` | `(href, attribs) => void` | Override the default `Linking.openURL` link handler. |
60
+ | `ignoredDomTags` | `string[]` | Tags to drop during parse (subtree removed). |
61
+ | `ignoredStyles` | `string[]` | CSS properties to drop. Accepts kebab-case (`background-color`) or camelCase (`backgroundColor`). |
62
+ | `defaultTextProps` | `TextProps` | Spread onto every `<Text>`. |
63
+ | `defaultViewProps` | `ViewProps` | Spread onto every `<View>`. |
64
+ | `textSelectable` | `boolean` | Shortcut for `defaultTextProps.selectable = true`. |
65
+
66
+ `StyleInput` is `ResolvedStyle | string` — every style map accepts either an RN-style object *or* a CSS declarations string:
67
+
68
+ ```tsx
69
+ tagsStyles={{
70
+ h1: { color: 'red', fontSize: 24 },
71
+ h2: 'color: blue; font-size: 20px', // CSS string works too
72
+ }}
73
+ ```
74
+
75
+ ## Supported tags
76
+
77
+ **Block:** `p`, `div`, `h1–h6`, `ul`, `ol`, `li`, `pre`, `blockquote`, `hr`, `table`, `thead`, `tbody`, `tfoot`, `tr`, `th`, `td`, `caption`.
78
+
79
+ **Inline:** `span`, `strong`/`b`, `em`/`i`, `u`, `s`/`del`/`strike`, `ins`, `mark`, `small`, `code`, `a`, `br`, `img`.
80
+
81
+ ## Supported CSS
82
+
83
+ - Typography: `color`, `font-size` (px), `font-family`, `font-weight`, `font-style`, `text-align`, `text-decoration`, `line-height` (px)
84
+ - Box model: `margin` (shorthand + individual sides), `padding` (shorthand + individual sides), `background-color`
85
+ - Colors: hex, `rgb()`, `rgba()`, `hsl()`, named (passed through to RN's color system)
86
+ - Units: **`px` only** for now — `em`/`rem`/`%` are not resolved yet
87
+
88
+ ## Examples
89
+
90
+ ### `stylesheet` — real CSS with selectors
91
+
92
+ ```tsx
93
+ const css = `
94
+ article.card {
95
+ background-color: #fafbfc;
96
+ padding: 12px;
97
+ }
98
+ article.card h3 {
99
+ color: #1a73e8;
100
+ }
101
+ .highlight {
102
+ background-color: #fff3a3;
103
+ }
104
+ h1 > span {
105
+ font-weight: bold;
106
+ }
107
+ `;
108
+
109
+ <HtmlRenderer html={html} stylesheet={css} />;
110
+ ```
111
+
112
+ Supports: type (`h1`), class (`.foo`), id (`#bar`), universal (`*`), compound (`h1.big#hero`), descendant (`article span`), child (`h1 > span`), selector lists (`h1, h2`). Specificity and source order work per the CSS spec.
113
+
114
+ Not supported: pseudo-classes, pseudo-elements, attribute selectors, sibling combinators (`+`, `~`), `@media` queries.
115
+
116
+ ### Custom renderer
117
+
118
+ ```tsx
119
+ import { type CustomRenderer } from '@nikpnevmatikos/html-renderer';
120
+
121
+ const customRenderers: Record<string, CustomRenderer> = {
122
+ h1: (node, defaultRender) => (
123
+ <View style={{ borderBottomWidth: 2, borderBottomColor: 'blue' }}>
124
+ {defaultRender()}
125
+ </View>
126
+ ),
127
+ };
128
+
129
+ <HtmlRenderer html={html} customRenderers={customRenderers} />;
130
+ ```
131
+
132
+ ### Custom HTML element models
133
+
134
+ Define your own tags that behave like real HTML:
135
+
136
+ ```tsx
137
+ import { type HTMLElementModel } from '@nikpnevmatikos/html-renderer';
138
+
139
+ const customHTMLElementModels: Record<string, HTMLElementModel> = {
140
+ 'my-card': {
141
+ display: 'block',
142
+ tagDefaultStyle: { backgroundColor: '#eef', padding: 12 },
143
+ },
144
+ 'x-spacer': {
145
+ display: 'block',
146
+ isVoid: true, // ignore any children
147
+ tagDefaultStyle: { height: 20 },
148
+ },
149
+ };
150
+
151
+ <HtmlRenderer
152
+ html="<my-card>hello</my-card>"
153
+ customHTMLElementModels={customHTMLElementModels}
154
+ />;
155
+ ```
156
+
157
+ ### DOM transform hook
158
+
159
+ ```tsx
160
+ import { type TransformDom } from '@nikpnevmatikos/html-renderer';
161
+
162
+ const sanitize: TransformDom = (dom) => walk(dom);
163
+
164
+ function walk(nodes) {
165
+ return nodes.map((n) => {
166
+ if (n.type === 'element') {
167
+ return { ...n, attribs: { ...n.attribs, onclick: '' }, children: walk(n.children) };
168
+ }
169
+ return n;
170
+ });
171
+ }
172
+
173
+ <HtmlRenderer html={html} transformDom={sanitize} />;
174
+ ```
175
+
176
+ ### Link handling
177
+
178
+ ```tsx
179
+ import { type OnLinkPress } from '@nikpnevmatikos/html-renderer';
180
+
181
+ const onLinkPress: OnLinkPress = (href, attribs) => {
182
+ if (attribs.target === '_blank') {
183
+ void Linking.openURL(href);
184
+ } else {
185
+ navigation.navigate('InAppBrowser', { url: href });
186
+ }
187
+ };
188
+
189
+ <HtmlRenderer html={html} onLinkPress={onLinkPress} />;
190
+ ```
191
+
192
+ ### Auto-fit images
193
+
194
+ ```tsx
195
+ import { Dimensions } from 'react-native';
196
+
197
+ const contentWidth = Dimensions.get('window').width - 32;
198
+
199
+ <HtmlRenderer html={html} contentWidth={contentWidth} />;
200
+ ```
201
+
202
+ ### Renderers props (per-renderer config)
203
+
204
+ ```tsx
205
+ <HtmlRenderer
206
+ html={html}
207
+ renderersProps={{
208
+ ol: { startIndex: 5, markerTextStyle: { color: '#888' } },
209
+ ul: { markerTextStyle: { color: 'red' } },
210
+ img: { initialDimensions: { width: 300, height: 200 } },
211
+ }}
212
+ />
213
+ ```
214
+
215
+ ## Cascade order
216
+
217
+ From lowest to highest priority:
218
+
219
+ ```
220
+ 1. baseStyle (HtmlRenderer prop — root defaults)
221
+ 2. Built-in tag defaults (h1 bold, strong bold, etc.)
222
+ 3. stylesheet matches (by selector specificity + source order)
223
+ 4. tagsStyles (per-tag programmatic override)
224
+ 5. classesStyles (by matched class)
225
+ 6. idsStyles (by matched id)
226
+ 7. Inline style="..." (highest — HTML inline always wins)
227
+ ```
228
+
229
+ ## How it works
230
+
231
+ ```
232
+ HTML string
233
+ └─ parseHtml (htmlparser2) → DOM tree
234
+ └─ transformDom? (optional) → DOM tree
235
+ └─ buildRenderTree → Render tree
236
+ └─ resolveStyles (full cascade per element)
237
+ └─ hoistBlocks (fragment inline-wrapping-block)
238
+ └─ collapseWhitespace (CSS whitespace rules)
239
+ └─ Renderer → <View> / <Text> / <Image>
240
+ ```
241
+
242
+ Styles resolve at build time into a single `ResolvedStyle` per element. At render time, `splitStyle` partitions each style into View-applicable and Text-applicable halves and applies them to the correct component.
243
+
244
+ ## Current limitations
245
+
246
+ Actively on the roadmap:
247
+
248
+ - **No `rowspan`** on tables (colspan works).
249
+ - **CSS units** beyond `px` — `em`, `rem`, `%` not resolved yet.
250
+ - **Forms** `<input>`, `<textarea>`, `<button>`, `<select>` not yet rendered (planned for core, pure-JS via RN's `TextInput` / `Pressable`).
251
+ - **Stylesheet features** — pseudo-classes (`:first-child`, `:nth-child`), attribute selectors (`[type="text"]`), and `@media` queries not yet supported.
252
+ - **Advanced CSS** — transforms, opacity, individual `border-*` sides, `border-radius`, flex/grid display modes.
253
+ - **Intrinsic table column widths** columns render equal-width (`flex: 1`); `width` on `<col>` / cells is ignored.
254
+
255
+ ## Plugin packages (planned)
256
+
257
+ Features that need native dependencies ship as separate packages so the core stays zero-native-modules. Each uses the core's `customRenderers` and `customHTMLElementModels` APIs — no core changes needed to add them.
258
+
259
+ | Tags | Planned package | Native peer dep |
260
+ |---|---|---|
261
+ | `<iframe>` | `@nikpnevmatikos/html-renderer-webview` | `react-native-webview` |
262
+ | `<video>`, `<audio>` | `@nikpnevmatikos/html-renderer-video` | `expo-video` or `react-native-video` |
263
+ | `<svg>` | `@nikpnevmatikos/html-renderer-svg` | `react-native-svg` |
264
+
265
+ Until these ship, you can wire any of these tags yourself via `customRenderers` — the same API the plugins will use. See the "Custom renderer" and "Custom HTML element models" examples above.
266
+
267
+ ## Development
268
+
269
+ ```bash
270
+ npm install # installs all workspace deps
271
+ npm run dev # tsc --watch on core
272
+ npm test # jest — 100+ tests
273
+ npm run typecheck # tsc --noEmit on core
274
+ npm run build # build core to dist
275
+
276
+ # live example app
277
+ cd example && npm start
278
+ ```
279
+
280
+ ## License
281
+
282
+ MIT — see [LICENSE](./LICENSE).
@@ -1 +1 @@
1
- {"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../../src/parser/parse.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAExC,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,EAAE,CAKjD"}
1
+ {"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../../src/parser/parse.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAUxC,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,EAAE,CAKjD"}