@nikpnevmatikos/html-renderer 0.1.0-alpha.3 → 0.2.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/LICENSE +21 -21
- package/README.md +297 -279
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/parser/parse.d.ts.map +1 -1
- package/dist/parser/parse.js +22 -1
- package/dist/parser/parse.js.map +1 -1
- package/dist/render-tree/build.d.ts +1 -0
- package/dist/render-tree/build.d.ts.map +1 -1
- package/dist/render-tree/build.js +35 -6
- package/dist/render-tree/build.js.map +1 -1
- package/dist/renderer/RenderedImage.js +2 -2
- package/dist/renderer/Renderer.d.ts.map +1 -1
- package/dist/renderer/Renderer.js +59 -44
- package/dist/renderer/Renderer.js.map +1 -1
- package/dist/styles/root.d.ts +18 -0
- package/dist/styles/root.d.ts.map +1 -0
- package/dist/styles/root.js +24 -0
- package/dist/styles/root.js.map +1 -0
- package/package.json +75 -74
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,279 +1,297 @@
|
|
|
1
|
-
# Html-Renderer
|
|
2
|
-
|
|
3
|
-
[](https://www.npmjs.com/package/@nikpnevmatikos/html-renderer)
|
|
4
|
-
[](https://github.com/NikPnevmatikos/Html-Renderer/actions/workflows/ci.yml)
|
|
5
|
-
[](./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
|
-
|
|
10
|
-
|
|
11
|
-
- **
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
| `
|
|
46
|
-
| `
|
|
47
|
-
| `
|
|
48
|
-
| `
|
|
49
|
-
| `
|
|
50
|
-
| `
|
|
51
|
-
| `
|
|
52
|
-
| `
|
|
53
|
-
| `
|
|
54
|
-
| `
|
|
55
|
-
| `
|
|
56
|
-
| `
|
|
57
|
-
| `
|
|
58
|
-
| `
|
|
59
|
-
| `
|
|
60
|
-
| `
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
-
|
|
82
|
-
-
|
|
83
|
-
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
```
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
1
|
+
# Html-Renderer
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@nikpnevmatikos/html-renderer)
|
|
4
|
+
[](https://github.com/NikPnevmatikos/Html-Renderer/actions/workflows/ci.yml)
|
|
5
|
+
[](./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
|
+
- **Zero native code** — works on iOS, Android, Web (via `react-native-web`), and Expo Go without a dev build.
|
|
10
|
+
- **Fabric (new architecture) compatible** out of the box.
|
|
11
|
+
- **Real CSS stylesheet support** — a `stylesheet` prop that accepts actual CSS with selectors and specificity.
|
|
12
|
+
- Transient render tree model: HTML → DOM → resolved render tree → `<Text>` / `<View>` / `<Image>`.
|
|
13
|
+
- Full style inheritance, CSS cascade, and the box-model basics.
|
|
14
|
+
- Extensible via custom renderers, custom element models, DOM transform hooks, and per-renderer config.
|
|
15
|
+
- **Entity-encoded HTML** (`<p>hello</p>` from CMS/API backends, even double-encoded) is auto-detected and rendered as HTML — no pre-decoding needed.
|
|
16
|
+
- 130+ unit tests, typed end-to-end.
|
|
17
|
+
|
|
18
|
+
## Install
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install @nikpnevmatikos/html-renderer
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Peer dependencies: `react >= 18`, `react-native >= 0.73`.
|
|
25
|
+
|
|
26
|
+
## Quick start
|
|
27
|
+
|
|
28
|
+
```tsx
|
|
29
|
+
import { HtmlRenderer } from '@nikpnevmatikos/html-renderer';
|
|
30
|
+
|
|
31
|
+
export default function Screen() {
|
|
32
|
+
return (
|
|
33
|
+
<HtmlRenderer
|
|
34
|
+
html={`<h1>Hello</h1><p>This is <strong>bold</strong> and <a href="https://x.dev">a link</a>.</p>`}
|
|
35
|
+
/>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Props
|
|
41
|
+
|
|
42
|
+
| Prop | Type | Description |
|
|
43
|
+
|---|---|---|
|
|
44
|
+
| `html` | `string` | The HTML source to render. |
|
|
45
|
+
| `baseStyle` | `ResolvedStyle` | Root style. Inherited text props cascade to all content; box props (background, padding, …) style the root container. |
|
|
46
|
+
| `stylesheet` | `string` | A CSS stylesheet with real selectors (type, class, id, descendant, child). |
|
|
47
|
+
| `tagsStyles` | `Record<tag, StyleInput>` | Per-tag style override (`{ h1: {...} }`). `body` is special: it styles the document root even when the HTML is a fragment with no `<body>` tag — see below. |
|
|
48
|
+
| `classesStyles` | `Record<class, StyleInput>` | Style by `class` attribute. |
|
|
49
|
+
| `idsStyles` | `Record<id, StyleInput>` | Style by `id` attribute. |
|
|
50
|
+
| `customRenderers` | `Record<tag, CustomRenderer>` | Replace or wrap the renderer for any tag. |
|
|
51
|
+
| `customHTMLElementModels` | `Record<tag, HTMLElementModel>` | Define new tags with custom block/inline semantics and default styles. |
|
|
52
|
+
| `renderersProps` | `Record<tag, Record<string, unknown>>` | Per-renderer config. Built-in consumers: `ol.startIndex`, `ul/ol.markerTextStyle`, `img.initialDimensions`. |
|
|
53
|
+
| `contentWidth` | `number` | Max render width. Images wider than this scale down proportionally. |
|
|
54
|
+
| `transformDom` | `(dom: DomNode[]) => DomNode[]` | Runs after parse, before build. Use for sanitization or tag rewrites. |
|
|
55
|
+
| `onLinkPress` | `(href, attribs) => void` | Override the default `Linking.openURL` link handler. |
|
|
56
|
+
| `ignoredDomTags` | `string[]` | Tags to drop during parse (subtree removed). Merged with the built-in defaults: `head`, `title`, `style`, `script`, `link`, `meta`, `base`. |
|
|
57
|
+
| `ignoredStyles` | `string[]` | CSS properties to drop. Accepts kebab-case (`background-color`) or camelCase (`backgroundColor`). |
|
|
58
|
+
| `defaultTextProps` | `TextProps` | Spread onto every `<Text>`. |
|
|
59
|
+
| `defaultViewProps` | `ViewProps` | Spread onto every `<View>`. |
|
|
60
|
+
| `textSelectable` | `boolean` | Shortcut for `defaultTextProps.selectable = true`. |
|
|
61
|
+
|
|
62
|
+
`StyleInput` is `ResolvedStyle | string` — every style map accepts either an RN-style object *or* a CSS declarations string:
|
|
63
|
+
|
|
64
|
+
```tsx
|
|
65
|
+
tagsStyles={{
|
|
66
|
+
h1: { color: 'red', fontSize: 24 },
|
|
67
|
+
h2: 'color: blue; font-size: 20px', // CSS string works too
|
|
68
|
+
}}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Supported tags
|
|
72
|
+
|
|
73
|
+
**Block:** `p`, `div`, `h1–h6`, `ul`, `ol`, `li`, `pre`, `blockquote`, `hr`, `table`, `thead`, `tbody`, `tfoot`, `tr`, `th`, `td`, `caption`.
|
|
74
|
+
|
|
75
|
+
**Inline:** `span`, `strong`/`b`, `em`/`i`, `u`, `s`/`del`/`strike`, `ins`, `mark`, `small`, `code`, `a`, `br`, `img`.
|
|
76
|
+
|
|
77
|
+
**Documents:** full-document HTML works too — literal `<html>`/`<body>` render as plain block containers, and `<head>`, `<title>`, `<style>`, `<script>`, `<link>`, `<meta>`, `<base>` are ignored by default.
|
|
78
|
+
|
|
79
|
+
## Supported CSS
|
|
80
|
+
|
|
81
|
+
- Typography: `color`, `font-size` (px), `font-family`, `font-weight`, `font-style`, `text-align`, `text-decoration`, `text-transform`, `letter-spacing`, `line-height` (px)
|
|
82
|
+
- Box model: `margin` / `padding` (shorthands + individual sides), `background-color`, the `border` family (shorthands, per-side widths/colors, `border-style`, `border-radius` including per-corner), `width`/`height` with `min-`/`max-` variants
|
|
83
|
+
- Other: `opacity`, `display` (`flex` and `none`)
|
|
84
|
+
- Colors: hex, `rgb()`, `rgba()`, `hsl()`, named (passed through to RN's color system)
|
|
85
|
+
- Units: **`px` only** for now — `em`/`rem` are not resolved yet, and `%` works only on `width`/`height`
|
|
86
|
+
|
|
87
|
+
## Examples
|
|
88
|
+
|
|
89
|
+
### Root styles — `baseStyle` and `tagsStyles.body`
|
|
90
|
+
|
|
91
|
+
`tagsStyles.body` styles the document root even when the source HTML is a fragment with no `<body>` element — as if the content were wrapped in a synthetic body. Inherited text properties cascade into all content; box properties are applied once to the root container. This makes it the natural way to set the root text color:
|
|
92
|
+
|
|
93
|
+
```tsx
|
|
94
|
+
<HtmlRenderer
|
|
95
|
+
html="<p>Hello</p>"
|
|
96
|
+
tagsStyles={{ body: { color: 'white' } }} // text renders white
|
|
97
|
+
/>
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
`baseStyle` does the same thing one rung lower in the cascade (`tagsStyles.body` wins where both set a property). If the HTML contains a literal `<body>` tag, `tagsStyles.body` is applied on that element instead, so box props are never applied twice.
|
|
101
|
+
|
|
102
|
+
### `stylesheet` — real CSS with selectors
|
|
103
|
+
|
|
104
|
+
```tsx
|
|
105
|
+
const css = `
|
|
106
|
+
article.card {
|
|
107
|
+
background-color: #fafbfc;
|
|
108
|
+
padding: 12px;
|
|
109
|
+
}
|
|
110
|
+
article.card h3 {
|
|
111
|
+
color: #1a73e8;
|
|
112
|
+
}
|
|
113
|
+
.highlight {
|
|
114
|
+
background-color: #fff3a3;
|
|
115
|
+
}
|
|
116
|
+
h1 > span {
|
|
117
|
+
font-weight: bold;
|
|
118
|
+
}
|
|
119
|
+
`;
|
|
120
|
+
|
|
121
|
+
<HtmlRenderer html={html} stylesheet={css} />;
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
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.
|
|
125
|
+
|
|
126
|
+
Not supported: pseudo-classes, pseudo-elements, attribute selectors, sibling combinators (`+`, `~`), `@media` queries.
|
|
127
|
+
|
|
128
|
+
### Custom renderer
|
|
129
|
+
|
|
130
|
+
```tsx
|
|
131
|
+
import { type CustomRenderer } from '@nikpnevmatikos/html-renderer';
|
|
132
|
+
|
|
133
|
+
const customRenderers: Record<string, CustomRenderer> = {
|
|
134
|
+
h1: (node, defaultRender) => (
|
|
135
|
+
<View style={{ borderBottomWidth: 2, borderBottomColor: 'blue' }}>
|
|
136
|
+
{defaultRender()}
|
|
137
|
+
</View>
|
|
138
|
+
),
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
<HtmlRenderer html={html} customRenderers={customRenderers} />;
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Custom HTML element models
|
|
145
|
+
|
|
146
|
+
Define your own tags that behave like real HTML:
|
|
147
|
+
|
|
148
|
+
```tsx
|
|
149
|
+
import { type HTMLElementModel } from '@nikpnevmatikos/html-renderer';
|
|
150
|
+
|
|
151
|
+
const customHTMLElementModels: Record<string, HTMLElementModel> = {
|
|
152
|
+
'my-card': {
|
|
153
|
+
display: 'block',
|
|
154
|
+
tagDefaultStyle: { backgroundColor: '#eef', padding: 12 },
|
|
155
|
+
},
|
|
156
|
+
'x-spacer': {
|
|
157
|
+
display: 'block',
|
|
158
|
+
isVoid: true, // ignore any children
|
|
159
|
+
tagDefaultStyle: { height: 20 },
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
<HtmlRenderer
|
|
164
|
+
html="<my-card>hello</my-card>"
|
|
165
|
+
customHTMLElementModels={customHTMLElementModels}
|
|
166
|
+
/>;
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### DOM transform hook
|
|
170
|
+
|
|
171
|
+
```tsx
|
|
172
|
+
import { type TransformDom } from '@nikpnevmatikos/html-renderer';
|
|
173
|
+
|
|
174
|
+
const sanitize: TransformDom = (dom) => walk(dom);
|
|
175
|
+
|
|
176
|
+
function walk(nodes) {
|
|
177
|
+
return nodes.map((n) => {
|
|
178
|
+
if (n.type === 'element') {
|
|
179
|
+
return { ...n, attribs: { ...n.attribs, onclick: '' }, children: walk(n.children) };
|
|
180
|
+
}
|
|
181
|
+
return n;
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
<HtmlRenderer html={html} transformDom={sanitize} />;
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Link handling
|
|
189
|
+
|
|
190
|
+
```tsx
|
|
191
|
+
import { type OnLinkPress } from '@nikpnevmatikos/html-renderer';
|
|
192
|
+
|
|
193
|
+
const onLinkPress: OnLinkPress = (href, attribs) => {
|
|
194
|
+
if (attribs.target === '_blank') {
|
|
195
|
+
void Linking.openURL(href);
|
|
196
|
+
} else {
|
|
197
|
+
navigation.navigate('InAppBrowser', { url: href });
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
<HtmlRenderer html={html} onLinkPress={onLinkPress} />;
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Auto-fit images
|
|
205
|
+
|
|
206
|
+
```tsx
|
|
207
|
+
import { Dimensions } from 'react-native';
|
|
208
|
+
|
|
209
|
+
const contentWidth = Dimensions.get('window').width - 32;
|
|
210
|
+
|
|
211
|
+
<HtmlRenderer html={html} contentWidth={contentWidth} />;
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Renderers props (per-renderer config)
|
|
215
|
+
|
|
216
|
+
```tsx
|
|
217
|
+
<HtmlRenderer
|
|
218
|
+
html={html}
|
|
219
|
+
renderersProps={{
|
|
220
|
+
ol: { startIndex: 5, markerTextStyle: { color: '#888' } },
|
|
221
|
+
ul: { markerTextStyle: { color: 'red' } },
|
|
222
|
+
img: { initialDimensions: { width: 300, height: 200 } },
|
|
223
|
+
}}
|
|
224
|
+
/>
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## Cascade order
|
|
228
|
+
|
|
229
|
+
From lowest to highest priority:
|
|
230
|
+
|
|
231
|
+
```
|
|
232
|
+
1. baseStyle (HtmlRenderer prop — root defaults)
|
|
233
|
+
2. tagsStyles.body (document root — applies even without a <body> tag)
|
|
234
|
+
3. Built-in tag defaults (h1 bold, strong bold, etc.)
|
|
235
|
+
4. stylesheet matches (by selector specificity + source order)
|
|
236
|
+
5. tagsStyles (per-tag programmatic override)
|
|
237
|
+
6. classesStyles (by matched class)
|
|
238
|
+
7. idsStyles (by matched id)
|
|
239
|
+
8. Inline style="..." (highest — HTML inline always wins)
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
Root styles (1–2) reach descendants through inheritance, so only inherited text properties cascade down — and any element-level match (3–8) overrides them.
|
|
243
|
+
|
|
244
|
+
## How it works
|
|
245
|
+
|
|
246
|
+
```
|
|
247
|
+
HTML string
|
|
248
|
+
└─ parseHtml (htmlparser2) → DOM tree
|
|
249
|
+
└─ transformDom? (optional) → DOM tree
|
|
250
|
+
└─ buildRenderTree → Render tree
|
|
251
|
+
└─ resolveStyles (full cascade per element)
|
|
252
|
+
└─ hoistBlocks (fragment inline-wrapping-block)
|
|
253
|
+
└─ collapseWhitespace (CSS whitespace rules)
|
|
254
|
+
└─ Renderer → <View> / <Text> / <Image>
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
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.
|
|
258
|
+
|
|
259
|
+
## Current limitations
|
|
260
|
+
|
|
261
|
+
Actively on the roadmap:
|
|
262
|
+
|
|
263
|
+
- **No `rowspan`** on tables (colspan works).
|
|
264
|
+
- **CSS units** beyond `px` — `em`, `rem` not resolved yet; `%` only on `width`/`height`.
|
|
265
|
+
- **Forms** — `<input>`, `<textarea>`, `<button>`, `<select>` not yet rendered (planned for core, pure-JS via RN's `TextInput` / `Pressable`).
|
|
266
|
+
- **Stylesheet features** — pseudo-classes (`:first-child`, `:nth-child`), attribute selectors (`[type="text"]`), and `@media` queries not yet supported.
|
|
267
|
+
- **Advanced CSS** — transforms, flex/grid layout of HTML content.
|
|
268
|
+
- **Intrinsic table column widths** — columns render equal-width (`flex: 1`); `width` on `<col>` / cells is ignored.
|
|
269
|
+
|
|
270
|
+
## Plugin packages (planned)
|
|
271
|
+
|
|
272
|
+
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.
|
|
273
|
+
|
|
274
|
+
| Tags | Planned package | Native peer dep |
|
|
275
|
+
|---|---|---|
|
|
276
|
+
| `<iframe>` | `@nikpnevmatikos/html-renderer-webview` | `react-native-webview` |
|
|
277
|
+
| `<video>`, `<audio>` | `@nikpnevmatikos/html-renderer-video` | `expo-video` or `react-native-video` |
|
|
278
|
+
| `<svg>` | `@nikpnevmatikos/html-renderer-svg` | `react-native-svg` |
|
|
279
|
+
|
|
280
|
+
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.
|
|
281
|
+
|
|
282
|
+
## Development
|
|
283
|
+
|
|
284
|
+
```bash
|
|
285
|
+
npm install # installs all workspace deps
|
|
286
|
+
npm run dev # tsc --watch on core
|
|
287
|
+
npm test # jest — 130+ tests
|
|
288
|
+
npm run typecheck # tsc --noEmit on core
|
|
289
|
+
npm run build # build core to dist
|
|
290
|
+
|
|
291
|
+
# live example app
|
|
292
|
+
cd example && npm start
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
## License
|
|
296
|
+
|
|
297
|
+
MIT — see [LICENSE](./LICENSE).
|
package/dist/index.d.ts
CHANGED
|
@@ -4,4 +4,6 @@ export type { DomNode, DomElement, DomText, HTMLElementModel, RenderNode, Render
|
|
|
4
4
|
export { parseHtml } from './parser/parse';
|
|
5
5
|
export { buildRenderTree } from './render-tree/build';
|
|
6
6
|
export { parseInlineStyle } from './styles/parse-inline';
|
|
7
|
+
export { resolveRootStyle, ROOT_DEFAULT_STYLE } from './styles/root';
|
|
8
|
+
export type { RootStyleOptions } from './styles/root';
|
|
7
9
|
//# sourceMappingURL=index.d.ts.map
|