@nikpnevmatikos/html-renderer 0.1.0-alpha.3 → 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 +21 -21
- package/README.md +282 -279
- 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/renderer/RenderedImage.js +2 -2
- package/dist/renderer/Renderer.js +39 -39
- 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,282 @@
|
|
|
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
|
-
> **Status:** currently in alpha (`0.1.0-alpha.
|
|
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
|
-
-
|
|
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
|
-
npm
|
|
271
|
-
npm run
|
|
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
|
+
> **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** (`<p>hello</p>` 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":"
|
|
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"}
|
package/dist/parser/parse.js
CHANGED
|
@@ -1,10 +1,31 @@
|
|
|
1
1
|
import { parseDocument } from 'htmlparser2';
|
|
2
|
+
import { decodeHTML } from 'entities';
|
|
3
|
+
// An entity-encoded tag opening, e.g. "<p", "</span", "<div".
|
|
4
|
+
// "(?:amp;)*" also catches double-encoded input such as "&lt;p".
|
|
5
|
+
const ENCODED_TAG = /&(?:amp;)*(?:lt|#0*60|#x0*3c);[a-zA-Z!/]/i;
|
|
6
|
+
// A real tag opening, e.g. "<p" or "</span".
|
|
7
|
+
const REAL_TAG = /<[a-zA-Z!/]/;
|
|
8
|
+
// Bound on decode passes so nested encoding resolves without looping forever.
|
|
9
|
+
const MAX_DECODE_PASSES = 3;
|
|
2
10
|
export function parseHtml(html) {
|
|
3
|
-
const doc = parseDocument(html, { decodeEntities: true });
|
|
11
|
+
const doc = parseDocument(decodeIfEncoded(html), { decodeEntities: true });
|
|
4
12
|
return doc.children
|
|
5
13
|
.map(toDomNode)
|
|
6
14
|
.filter((n) => n !== null);
|
|
7
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* Some backends deliver HTML entity-escaped ("<p>hello</p>").
|
|
18
|
+
* Parsed directly that yields a single text node of literal markup, so when
|
|
19
|
+
* the input contains encoded tags but no real ones, decode it first. Input
|
|
20
|
+
* with any real tag is left untouched — there "<" is intentional text.
|
|
21
|
+
*/
|
|
22
|
+
function decodeIfEncoded(html) {
|
|
23
|
+
let out = html;
|
|
24
|
+
for (let pass = 0; pass < MAX_DECODE_PASSES && !REAL_TAG.test(out) && ENCODED_TAG.test(out); pass++) {
|
|
25
|
+
out = decodeHTML(out);
|
|
26
|
+
}
|
|
27
|
+
return out;
|
|
28
|
+
}
|
|
8
29
|
function toDomNode(n) {
|
|
9
30
|
if (n.type === 'text') {
|
|
10
31
|
const t = n;
|
package/dist/parser/parse.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parse.js","sourceRoot":"","sources":["../../src/parser/parse.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"parse.js","sourceRoot":"","sources":["../../src/parser/parse.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAItC,wEAAwE;AACxE,qEAAqE;AACrE,MAAM,WAAW,GAAG,2CAA2C,CAAC;AAChE,6CAA6C;AAC7C,MAAM,QAAQ,GAAG,aAAa,CAAC;AAC/B,8EAA8E;AAC9E,MAAM,iBAAiB,GAAG,CAAC,CAAC;AAE5B,MAAM,UAAU,SAAS,CAAC,IAAY;IACpC,MAAM,GAAG,GAAG,aAAa,CAAC,eAAe,CAAC,IAAI,CAAC,EAAE,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3E,OAAO,GAAG,CAAC,QAAQ;SAChB,GAAG,CAAC,SAAS,CAAC;SACd,MAAM,CAAC,CAAC,CAAC,EAAgB,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC;AAC7C,CAAC;AAED;;;;;GAKG;AACH,SAAS,eAAe,CAAC,IAAY;IACnC,IAAI,GAAG,GAAG,IAAI,CAAC;IACf,KACE,IAAI,IAAI,GAAG,CAAC,EACZ,IAAI,GAAG,iBAAiB,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,EACxE,IAAI,EAAE,EACN,CAAC;QACD,GAAG,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC;IACxB,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,SAAS,CAAC,CAAO;IACxB,IAAI,CAAC,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;QACtB,MAAM,CAAC,GAAG,CAAS,CAAC;QACpB,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACxC,CAAC;IACD,IAAI,CAAC,CAAC,IAAI,KAAK,KAAK,EAAE,CAAC;QACrB,MAAM,EAAE,GAAG,CAAY,CAAC;QACxB,OAAO;YACL,IAAI,EAAE,SAAS;YACf,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,EAAE;YAC3B,OAAO,EAAE,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE;YAC1B,QAAQ,EAAE,EAAE,CAAC,QAAQ;iBAClB,GAAG,CAAC,SAAS,CAAC;iBACd,MAAM,CAAC,CAAC,CAAC,EAAgB,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC;SAC3C,CAAC;IACJ,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -30,8 +30,8 @@ export function RenderedImage({ node, contentWidth, initialDimensions, }) {
|
|
|
30
30
|
return <View style={styles.placeholder}/>;
|
|
31
31
|
}
|
|
32
32
|
if (resolvedSize.w === 0 || resolvedSize.h === 0) {
|
|
33
|
-
return (<View style={styles.broken}>
|
|
34
|
-
<Text style={styles.brokenLabel}>{node.alt ?? '[broken image]'}</Text>
|
|
33
|
+
return (<View style={styles.broken}>
|
|
34
|
+
<Text style={styles.brokenLabel}>{node.alt ?? '[broken image]'}</Text>
|
|
35
35
|
</View>);
|
|
36
36
|
}
|
|
37
37
|
const fitted = fit(resolvedSize.w, resolvedSize.h, contentWidth);
|
|
@@ -52,8 +52,8 @@ export function HtmlRenderer({ html, baseStyle, tagsStyles, classesStyles, idsSt
|
|
|
52
52
|
defaultViewProps: mergedViewProps,
|
|
53
53
|
};
|
|
54
54
|
const parentStyle = { ...ROOT_STYLE, ...(baseStyle ?? {}) };
|
|
55
|
-
return (<View {...mergedViewProps}>
|
|
56
|
-
{renderBlockChildren(tree, parentStyle, ctx)}
|
|
55
|
+
return (<View {...mergedViewProps}>
|
|
56
|
+
{renderBlockChildren(tree, parentStyle, ctx)}
|
|
57
57
|
</View>);
|
|
58
58
|
}
|
|
59
59
|
function groupBlockChildren(children) {
|
|
@@ -95,8 +95,8 @@ function renderSegment(seg, parentStyle, key, ctx) {
|
|
|
95
95
|
return renderBlockElement(seg.node, key, ctx);
|
|
96
96
|
case 'run': {
|
|
97
97
|
const { text: tStyle } = splitStyle(parentStyle);
|
|
98
|
-
return (<Text key={key} {...ctx.defaultTextProps} style={tStyle}>
|
|
99
|
-
{seg.nodes.map((n, i) => renderInline(n, i, ctx))}
|
|
98
|
+
return (<Text key={key} {...ctx.defaultTextProps} style={tStyle}>
|
|
99
|
+
{seg.nodes.map((n, i) => renderInline(n, i, ctx))}
|
|
100
100
|
</Text>);
|
|
101
101
|
}
|
|
102
102
|
}
|
|
@@ -108,8 +108,8 @@ function renderBlockElement(el, key, ctx) {
|
|
|
108
108
|
renderersProps: ctx.renderersProps,
|
|
109
109
|
contentWidth: ctx.contentWidth,
|
|
110
110
|
};
|
|
111
|
-
return (<React.Fragment key={key}>
|
|
112
|
-
{custom(el, () => renderBlockDefault(el, undefined, ctx), info)}
|
|
111
|
+
return (<React.Fragment key={key}>
|
|
112
|
+
{custom(el, () => renderBlockDefault(el, undefined, ctx), info)}
|
|
113
113
|
</React.Fragment>);
|
|
114
114
|
}
|
|
115
115
|
return renderBlockDefault(el, key, ctx);
|
|
@@ -120,41 +120,41 @@ function renderBlockDefault(el, key, ctx) {
|
|
|
120
120
|
return (<View key={key} {...ctx.defaultViewProps} style={[styles.hr, vStyle]}/>);
|
|
121
121
|
}
|
|
122
122
|
if (el.tag === 'ul' || el.tag === 'ol') {
|
|
123
|
-
return (<View key={key} {...ctx.defaultViewProps} style={[styles.block, styles.list, vStyle]}>
|
|
124
|
-
{renderBlockChildren(el.children, el.style, ctx)}
|
|
123
|
+
return (<View key={key} {...ctx.defaultViewProps} style={[styles.block, styles.list, vStyle]}>
|
|
124
|
+
{renderBlockChildren(el.children, el.style, ctx)}
|
|
125
125
|
</View>);
|
|
126
126
|
}
|
|
127
127
|
if (el.tag === 'li' && el.listMarker !== undefined) {
|
|
128
128
|
const listKey = el.listOrdered ? 'ol' : 'ul';
|
|
129
129
|
const listProps = ctx.renderersProps[listKey];
|
|
130
130
|
const markerStyleOverride = listProps?.markerTextStyle;
|
|
131
|
-
return (<View key={key} {...ctx.defaultViewProps} style={[styles.listItem, vStyle]}>
|
|
132
|
-
<Text {...ctx.defaultTextProps} style={[tStyle, styles.listMarker, markerStyleOverride]}>
|
|
133
|
-
{el.listMarker}
|
|
134
|
-
</Text>
|
|
135
|
-
<View {...ctx.defaultViewProps} style={styles.listItemContent}>
|
|
136
|
-
{renderBlockChildren(el.children, el.style, ctx)}
|
|
137
|
-
</View>
|
|
131
|
+
return (<View key={key} {...ctx.defaultViewProps} style={[styles.listItem, vStyle]}>
|
|
132
|
+
<Text {...ctx.defaultTextProps} style={[tStyle, styles.listMarker, markerStyleOverride]}>
|
|
133
|
+
{el.listMarker}
|
|
134
|
+
</Text>
|
|
135
|
+
<View {...ctx.defaultViewProps} style={styles.listItemContent}>
|
|
136
|
+
{renderBlockChildren(el.children, el.style, ctx)}
|
|
137
|
+
</View>
|
|
138
138
|
</View>);
|
|
139
139
|
}
|
|
140
140
|
if (el.tag === 'pre') {
|
|
141
|
-
return (<View key={key} {...ctx.defaultViewProps} style={[styles.block, styles.preBlock, vStyle]}>
|
|
142
|
-
{renderBlockChildren(el.children, el.style, ctx)}
|
|
141
|
+
return (<View key={key} {...ctx.defaultViewProps} style={[styles.block, styles.preBlock, vStyle]}>
|
|
142
|
+
{renderBlockChildren(el.children, el.style, ctx)}
|
|
143
143
|
</View>);
|
|
144
144
|
}
|
|
145
145
|
if (el.tag === 'blockquote') {
|
|
146
|
-
return (<View key={key} {...ctx.defaultViewProps} style={[styles.block, styles.blockquote, vStyle]}>
|
|
147
|
-
{renderBlockChildren(el.children, el.style, ctx)}
|
|
146
|
+
return (<View key={key} {...ctx.defaultViewProps} style={[styles.block, styles.blockquote, vStyle]}>
|
|
147
|
+
{renderBlockChildren(el.children, el.style, ctx)}
|
|
148
148
|
</View>);
|
|
149
149
|
}
|
|
150
150
|
if (el.tag === 'table') {
|
|
151
|
-
return (<View key={key} {...ctx.defaultViewProps} style={[styles.table, vStyle]}>
|
|
152
|
-
{renderTableChildren(el.children, ctx)}
|
|
151
|
+
return (<View key={key} {...ctx.defaultViewProps} style={[styles.table, vStyle]}>
|
|
152
|
+
{renderTableChildren(el.children, ctx)}
|
|
153
153
|
</View>);
|
|
154
154
|
}
|
|
155
155
|
if (el.tag === 'thead' || el.tag === 'tbody' || el.tag === 'tfoot') {
|
|
156
|
-
return (<View key={key} {...ctx.defaultViewProps}>
|
|
157
|
-
{renderTableChildren(el.children, ctx)}
|
|
156
|
+
return (<View key={key} {...ctx.defaultViewProps}>
|
|
157
|
+
{renderTableChildren(el.children, ctx)}
|
|
158
158
|
</View>);
|
|
159
159
|
}
|
|
160
160
|
if (el.tag === 'tr') {
|
|
@@ -164,14 +164,14 @@ function renderBlockDefault(el, key, ctx) {
|
|
|
164
164
|
return renderTableCell(el, key, ctx);
|
|
165
165
|
}
|
|
166
166
|
if (el.tag === 'caption') {
|
|
167
|
-
return (<View key={key} {...ctx.defaultViewProps} style={[styles.tableCaption, vStyle]}>
|
|
168
|
-
{renderBlockChildren(el.children, el.style, ctx)}
|
|
167
|
+
return (<View key={key} {...ctx.defaultViewProps} style={[styles.tableCaption, vStyle]}>
|
|
168
|
+
{renderBlockChildren(el.children, el.style, ctx)}
|
|
169
169
|
</View>);
|
|
170
170
|
}
|
|
171
171
|
const a11yRole = blockA11yRole(el.tag);
|
|
172
172
|
const a11yLevel = HEADING_LEVEL[el.tag];
|
|
173
|
-
return (<View key={key} {...ctx.defaultViewProps} style={[styles.block, vStyle]} accessibilityRole={a11yRole} {...(a11yLevel !== undefined ? { 'aria-level': a11yLevel } : {})}>
|
|
174
|
-
{renderBlockChildren(el.children, el.style, ctx)}
|
|
173
|
+
return (<View key={key} {...ctx.defaultViewProps} style={[styles.block, vStyle]} accessibilityRole={a11yRole} {...(a11yLevel !== undefined ? { 'aria-level': a11yLevel } : {})}>
|
|
174
|
+
{renderBlockChildren(el.children, el.style, ctx)}
|
|
175
175
|
</View>);
|
|
176
176
|
}
|
|
177
177
|
function blockA11yRole(tag) {
|
|
@@ -195,22 +195,22 @@ function renderTableRow(tr, key, ctx) {
|
|
|
195
195
|
cells.push(c);
|
|
196
196
|
}
|
|
197
197
|
}
|
|
198
|
-
return (<View key={key} {...ctx.defaultViewProps} style={styles.tr}>
|
|
199
|
-
{cells.map((cell, i) => renderTableCell(cell, i, ctx))}
|
|
198
|
+
return (<View key={key} {...ctx.defaultViewProps} style={styles.tr}>
|
|
199
|
+
{cells.map((cell, i) => renderTableCell(cell, i, ctx))}
|
|
200
200
|
</View>);
|
|
201
201
|
}
|
|
202
202
|
function renderTableCell(cell, key, ctx) {
|
|
203
203
|
const { view: vStyle } = splitStyle(cell.style);
|
|
204
204
|
const flex = cell.colSpan ?? 1;
|
|
205
|
-
return (<View key={key} {...ctx.defaultViewProps} style={[styles.tableCell, { flex }, vStyle]}>
|
|
206
|
-
{renderBlockChildren(cell.children, cell.style, ctx)}
|
|
205
|
+
return (<View key={key} {...ctx.defaultViewProps} style={[styles.tableCell, { flex }, vStyle]}>
|
|
206
|
+
{renderBlockChildren(cell.children, cell.style, ctx)}
|
|
207
207
|
</View>);
|
|
208
208
|
}
|
|
209
209
|
function renderInline(node, key, ctx) {
|
|
210
210
|
if (node.kind === 'text') {
|
|
211
211
|
const { text: tStyle } = splitStyle(node.style);
|
|
212
|
-
return (<Text key={key} {...ctx.defaultTextProps} style={tStyle}>
|
|
213
|
-
{node.text}
|
|
212
|
+
return (<Text key={key} {...ctx.defaultTextProps} style={tStyle}>
|
|
213
|
+
{node.text}
|
|
214
214
|
</Text>);
|
|
215
215
|
}
|
|
216
216
|
if (node.kind === 'image') {
|
|
@@ -225,8 +225,8 @@ function renderInlineElement(el, key, ctx) {
|
|
|
225
225
|
renderersProps: ctx.renderersProps,
|
|
226
226
|
contentWidth: ctx.contentWidth,
|
|
227
227
|
};
|
|
228
|
-
return (<React.Fragment key={key}>
|
|
229
|
-
{custom(el, () => renderInlineDefault(el, undefined, ctx), info)}
|
|
228
|
+
return (<React.Fragment key={key}>
|
|
229
|
+
{custom(el, () => renderInlineDefault(el, undefined, ctx), info)}
|
|
230
230
|
</React.Fragment>);
|
|
231
231
|
}
|
|
232
232
|
return renderInlineDefault(el, key, ctx);
|
|
@@ -244,12 +244,12 @@ function renderInlineDefault(el, key, ctx) {
|
|
|
244
244
|
else {
|
|
245
245
|
void Linking.openURL(href);
|
|
246
246
|
}
|
|
247
|
-
}}>
|
|
248
|
-
{children}
|
|
247
|
+
}}>
|
|
248
|
+
{children}
|
|
249
249
|
</Text>);
|
|
250
250
|
}
|
|
251
|
-
return (<Text key={key} {...ctx.defaultTextProps} style={tStyle}>
|
|
252
|
-
{children}
|
|
251
|
+
return (<Text key={key} {...ctx.defaultTextProps} style={tStyle}>
|
|
252
|
+
{children}
|
|
253
253
|
</Text>);
|
|
254
254
|
}
|
|
255
255
|
const styles = StyleSheet.create({
|
package/package.json
CHANGED
|
@@ -1,74 +1,75 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@nikpnevmatikos/html-renderer",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
4
|
-
"description": "React Native HTML renderer in TypeScript — zero native modules, Fabric/Expo compatible. Supports tagsStyles, stylesheet with CSS selectors, custom renderers, and more.",
|
|
5
|
-
"author": "NikPnevmatikos",
|
|
6
|
-
"license": "MIT",
|
|
7
|
-
"homepage": "https://github.com/NikPnevmatikos/Html-Renderer#readme",
|
|
8
|
-
"bugs": {
|
|
9
|
-
"url": "https://github.com/NikPnevmatikos/Html-Renderer/issues"
|
|
10
|
-
},
|
|
11
|
-
"repository": {
|
|
12
|
-
"type": "git",
|
|
13
|
-
"url": "git+https://github.com/NikPnevmatikos/Html-Renderer.git",
|
|
14
|
-
"directory": "packages/core"
|
|
15
|
-
},
|
|
16
|
-
"main": "./dist/index.js",
|
|
17
|
-
"module": "./dist/index.js",
|
|
18
|
-
"types": "./dist/index.d.ts",
|
|
19
|
-
"react-native": "./dist/index.js",
|
|
20
|
-
"files": [
|
|
21
|
-
"dist",
|
|
22
|
-
"README.md",
|
|
23
|
-
"LICENSE"
|
|
24
|
-
],
|
|
25
|
-
"publishConfig": {
|
|
26
|
-
"access": "public"
|
|
27
|
-
},
|
|
28
|
-
"engines": {
|
|
29
|
-
"node": ">=20"
|
|
30
|
-
},
|
|
31
|
-
"sideEffects": false,
|
|
32
|
-
"scripts": {
|
|
33
|
-
"build": "tsc",
|
|
34
|
-
"dev": "tsc --watch --preserveWatchOutput",
|
|
35
|
-
"typecheck": "tsc --noEmit",
|
|
36
|
-
"test": "jest",
|
|
37
|
-
"clean": "rimraf dist"
|
|
38
|
-
},
|
|
39
|
-
"jest": {
|
|
40
|
-
"preset": "ts-jest",
|
|
41
|
-
"testEnvironment": "node",
|
|
42
|
-
"testMatch": [
|
|
43
|
-
"<rootDir>/src/**/*.test.ts"
|
|
44
|
-
]
|
|
45
|
-
},
|
|
46
|
-
"keywords": [
|
|
47
|
-
"react-native",
|
|
48
|
-
"html",
|
|
49
|
-
"renderer",
|
|
50
|
-
"expo",
|
|
51
|
-
"fabric",
|
|
52
|
-
"typescript",
|
|
53
|
-
"stylesheet",
|
|
54
|
-
"css",
|
|
55
|
-
"react-native-web"
|
|
56
|
-
],
|
|
57
|
-
"peerDependencies": {
|
|
58
|
-
"react": ">=18",
|
|
59
|
-
"react-native": ">=0.73"
|
|
60
|
-
},
|
|
61
|
-
"dependencies": {
|
|
62
|
-
"domhandler": "^5.0.3",
|
|
63
|
-
"
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
"@types/
|
|
68
|
-
"
|
|
69
|
-
"
|
|
70
|
-
"react
|
|
71
|
-
"
|
|
72
|
-
"
|
|
73
|
-
|
|
74
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@nikpnevmatikos/html-renderer",
|
|
3
|
+
"version": "0.1.0-alpha.4",
|
|
4
|
+
"description": "React Native HTML renderer in TypeScript — zero native modules, Fabric/Expo compatible. Supports tagsStyles, stylesheet with CSS selectors, custom renderers, and more.",
|
|
5
|
+
"author": "NikPnevmatikos",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"homepage": "https://github.com/NikPnevmatikos/Html-Renderer#readme",
|
|
8
|
+
"bugs": {
|
|
9
|
+
"url": "https://github.com/NikPnevmatikos/Html-Renderer/issues"
|
|
10
|
+
},
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/NikPnevmatikos/Html-Renderer.git",
|
|
14
|
+
"directory": "packages/core"
|
|
15
|
+
},
|
|
16
|
+
"main": "./dist/index.js",
|
|
17
|
+
"module": "./dist/index.js",
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"react-native": "./dist/index.js",
|
|
20
|
+
"files": [
|
|
21
|
+
"dist",
|
|
22
|
+
"README.md",
|
|
23
|
+
"LICENSE"
|
|
24
|
+
],
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public"
|
|
27
|
+
},
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=20"
|
|
30
|
+
},
|
|
31
|
+
"sideEffects": false,
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "tsc",
|
|
34
|
+
"dev": "tsc --watch --preserveWatchOutput",
|
|
35
|
+
"typecheck": "tsc --noEmit",
|
|
36
|
+
"test": "jest",
|
|
37
|
+
"clean": "rimraf dist"
|
|
38
|
+
},
|
|
39
|
+
"jest": {
|
|
40
|
+
"preset": "ts-jest",
|
|
41
|
+
"testEnvironment": "node",
|
|
42
|
+
"testMatch": [
|
|
43
|
+
"<rootDir>/src/**/*.test.ts"
|
|
44
|
+
]
|
|
45
|
+
},
|
|
46
|
+
"keywords": [
|
|
47
|
+
"react-native",
|
|
48
|
+
"html",
|
|
49
|
+
"renderer",
|
|
50
|
+
"expo",
|
|
51
|
+
"fabric",
|
|
52
|
+
"typescript",
|
|
53
|
+
"stylesheet",
|
|
54
|
+
"css",
|
|
55
|
+
"react-native-web"
|
|
56
|
+
],
|
|
57
|
+
"peerDependencies": {
|
|
58
|
+
"react": ">=18",
|
|
59
|
+
"react-native": ">=0.73"
|
|
60
|
+
},
|
|
61
|
+
"dependencies": {
|
|
62
|
+
"domhandler": "^5.0.3",
|
|
63
|
+
"entities": "^7.0.1",
|
|
64
|
+
"htmlparser2": "^10.0.0"
|
|
65
|
+
},
|
|
66
|
+
"devDependencies": {
|
|
67
|
+
"@types/jest": "^29.5.14",
|
|
68
|
+
"@types/react": "~19.1.0",
|
|
69
|
+
"jest": "^29.7.0",
|
|
70
|
+
"react": "19.1.0",
|
|
71
|
+
"react-native": "0.81.5",
|
|
72
|
+
"ts-jest": "^29.4.9",
|
|
73
|
+
"typescript": "~5.9.2"
|
|
74
|
+
}
|
|
75
|
+
}
|