@@ -11,18 +11,25 @@ hide_table_of_contents: false
1111draft : true
1212---
1313
14+ import APIReference from ' @site/src/components/APIReference' ;
15+
1416It has been over two months since the first final version of the Foundry
1517released has been made public.
1618
17- ## You Might not Need a Custom Component Renderer Anymore
19+ ## You Might not Need a Custom Component Renderer
1820
19- Model-based custom rendering via ` customHTMLElementModels ` prop has been a
20- lightweight alternative to custom (component) renderers since the early stages
21- of the Foundry release. However, it was limited to setting user agent styles
22- (` mixedUAStyles ` ), although those styles could be derived from the DOM node
23- element attributes (` getUADerivedStyleFromAttributes ` ).
21+ Model-based custom rendering via <APIReference name = " RenderHTMLProps"
22+ member = " customHTMLElementModels" /> prop has been a lightweight alternative to
23+ custom (component) renderers since the early stages of the Foundry release.
24+ However, it was limited to setting user agent styles (<APIReference
25+ name = " HTMLElementModel" member = " mixedUAStyles" />),
26+ although those styles could be derived from the DOM node element attributes
27+ (<APIReference name = " HTMLElementModel" member = " getUADerivedStyleFromAttributes" />).
2428
2529``` js title="An Example of Model-Based Rendering"
30+ import RenderHTML , { HTMLElementModel } from ' react-native-render-html' ;
31+
32+ // The eponym prop to pass to RenderHTML
2633const customHTMLElementModels = {
2734 ' blue-circle' : HTMLElementModel .fromCustomModel ({
2835 tagName: ' blue-circle' ,
@@ -38,33 +45,238 @@ const customHTMLElementModels = {
3845};
3946```
4047
41- Version 6.2 ships with a bunch of new properties for HTML element models which
42- should make model-based custom rendering much more prevalent. Let's take a tour!
48+ :::important
49+ Keep in mind that <APIReference name = " HTMLElementModel" member = " mixedUAStyles"
50+ /> has a lower specificity than styles passed to ` RenderHTML ` such as
51+ &ZeroWidthSpace ; <APIReference name = " RenderHTMLProps" member = " tagsStyles" />.
52+ :::
53+
54+ Version 6.2 ships with a bunch of new fields for HTML element models which
55+ should make model-based custom rendering more popular. Let's take a tour!
56+
57+ ### ` getMixedUAStyles ` almost features CSS selectors!
4358
44- ### ` getUADynamicMixedStyles `
59+ This field deprecates <APIReference name = " HTMLElementModel" member = " getUADerivedStyleFromAttributes" />; it's basically the same
60+ but its signature has changed: it receives the target ` tnode ` and DOM ` element ` , which lets us implement CSS-selector-like behaviors:
61+
62+ ``` js title="Conditionnaly remove margins of 'ol' direct descendents of 'p' elements."
63+ import RenderHTML, {
64+ defaultHTMLElementModels ,
65+ isDomElement
66+ } from ' react-native-render-html' ;
67+
68+ // The eponym prop to pass to RenderHTML
69+ const customHTMLElementModels = {
70+ ol: defaultHTMLElementModels .ol .extend ({
71+ getMixedUAStyles (tnode , element ) {
72+ if (isDomElement (element .parent ) && element .parent .tagName === ' p' ) {
73+ // This is equivalent to targetting a "p > ol" CSS selector.
74+ return {
75+ marginTop: 0 ,
76+ marginBottom: 0
77+ };
78+ }
79+ }
80+ })
81+ };
82+ ```
83+
84+ :::warning
85+ Beware that this ` tnode ` is an instance of <APIReference name = " TNodeDescriptor"
86+ />, which is a minimal ` tnode ` shape available during the Transient Render
87+ Engine creation. You will not have access to ` parent ` or ` children ` fields,
88+ since the hierarchy is yet in the making. This is why we are using the second
89+ argument, ` element ` , to query the DOM hierarchy instead. For this very reason,
90+ you are advised to use [ ` domutils ` ] ( https://github.com/fb55/domutils ) library
91+ to query the DOM and create your conditional styling rules.
92+ :::
4593
4694### ` reactNativeProps `
4795
48- ### ` getDynamicReactNativeProps `
96+ This field will set props to the native component during the rendering phase. It is an object with three optional properties:
97+
98+ 1 . ` text ` , to pass native props to the ` Text ` component when the renderer is textual;
99+ 2 . ` view ` , to pass native props to the ` View ` component when the renderer is block;
100+ 3 . ` native ` , to pass props to either ` View ` or ` Text ` components, irrespective of the renderer type.
101+
102+ The type definition for this object is <APIReference name = " ReactNativePropsDefinitions" />.
103+
104+ In the below example, we are defining a custom tag, ` nav-widget ` , and setting
105+ accessibility props to any underlying native component for the render phase. We
106+ can also define ` onPress ` , which will cause the renderer to use the
107+ &ZeroWidthSpace ; <APIReference name = " RenderHTMLProps" member = " GenericPressable" /> instead of
108+ default ` View ` for block renderers.
109+
110+ ``` js title="Defining React Native props in an HTML element model"
111+ import RenderHTML, {
112+ HTMLContentModel ,
113+ HTMLElementModel
114+ } from ' react-native-render-html' ;
115+
116+ // The eponym prop to pass to RenderHTML
117+ const customHTMLElementModels = {
118+ ' nav-widget' : HTMLElementModel .fromCustomModel ({
119+ tagName: ' nav-widget' ,
120+ contentModel: HTMLContentModel .block ,
121+ reactNativeProps: {
122+ native: {
123+ accessibilityRole: ' link' ,
124+ onPress () {
125+ console .info (' Pressed the nav widget!' );
126+ }
127+ }
128+ }
129+ })
130+ };
131+ ```
132+
133+ However, this field is somehow limited in that it cannot depend on ` tnode ` attributes.
134+ This is where <APIReference name = " HTMLElementModel" member = " getReactNativeProps" /> comes to the rescue!
135+
136+ ### ` getReactNativeProps `
137+
138+ The purpose of this field is identical to <APIReference name = " HTMLElementModel"
139+ member = " reactNativeProps" /> field. It only differs in that instead of a plain
140+ object, it is a method which takes two arguments, ` tnode ` (the Transient Node)
141+ and ` element ` (the DOM node) and returns a plain object (see <APIReference
142+ name = " ReactNativePropsDefinitions" />).
143+
144+ In the example below, a custom ` nav-widget ` tag is registered. This time, we
145+ are handling ` onPress ` events conditionally, based on attributes of the
146+ ` tnode ` . The snippets uses a phony API, ` appNavigatorControler ` , to navigate
147+ between screens. Such API is easy to implement with a globally-defined ref to a
148+ ` react-navigation ` "navigation" object.
149+
150+ :::important
151+ It is worth noting that you cannot use React hooks in any of element models functions. But you can use
152+ any ad-hoc API to emit events. If you really need React hooks, add a custom component renderer.
153+ :::
49154
50- ## Don't Forget you can Mix Model-Based and Component-Based Custom Rendering
155+ ``` js title="Defining React Native props based on the TNode in an HTML element model"
156+ import RenderHTML, {
157+ HTMLContentModel ,
158+ HTMLElementModel
159+ } from ' react-native-render-html' ;
160+ import appNavigatorControler from ' ./appNavigatorControler' ;
161+
162+ // The eponym prop to pass to RenderHTML
163+ const customHTMLElementModels = {
164+ ' nav-widget' : HTMLElementModel .fromCustomModel ({
165+ tagName: ' nav-widget' ,
166+ contentModel: HTMLContentModel .block ,
167+ getReactNativeProps (tnode ) {
168+ return {
169+ native: {
170+ accessibilityRole: ' link' ,
171+ onPress () {
172+ const targetScreen = tnode .attributes [' data-target' ];
173+ const targetParams = tnode .attributes [' data-params' ];
174+ appNavigatorControler .navigate (
175+ targetScreen,
176+ targetParams ? JSON .parse (targetParams) : null
177+ );
178+ }
179+ }
180+ };
181+ }
182+ })
183+ };
184+ ```
185+
186+ :::tip
187+ Don't forget that you can mix model-based and component-based rendering!
188+ :::
51189
52190## A Focus on Accessibility
53191
54192### Support for ` aria-label ` and ` aria-role ` Attributes
55193
194+ ` aria-label ` and ` aria-role ` HTML attributes roughly map to
195+ ` accessibilityLabel ` and ` accessibilityRole ` React Native props. The new
196+ Transient Render Engine which features React Native props generation will from now on
197+ translate both attributes to their React Native counterparts. The mapping rules
198+ for ` aria-role ` have been extracted from ` react-native-web ` .
199+
56200### Accessible ` <a> ` Tags
57201
58- ### Accessible ` <img> ` Tags
202+ ` <a> ` tags now receive an ` accessibilityRole="link" ` prop when their ` href `
203+ attribute is non-empty. Let's see now how we could set a custom generic hint by
204+ extending the HTML model:
59205
60- ### Accessible ` <h1-60> ` Tags
206+ ``` ts
207+ import RenderHTML , { defaultHTMLElementModels } from ' react-native-render-html' ;
61208
62- ### Other tags
209+ // The eponym prop to pass to RenderHTML
210+ const customHTMLElementModels = {
211+ a: defaultHTMLElementModels .a .extend ((aModel ) => ({
212+ nativeProps: {
213+ ... aModel .nativeProps ,
214+ accessibilityHint: ' Open in your system web browser.'
215+ }
216+ }))
217+ };
218+ ```
219+
220+ :::warning
221+ Because of a [ React Native
222+ limitation] ( https://github.com/facebook/react-native/issues/32004 ) , nested
223+ ` Text ` elements are not accessible, which means that the screen reader will not
224+ be able to identify ` <a> ` tags as links when grouped with other textual
225+ elements. Below is an example:
226+
227+ ``` html
228+ <p >
229+ Unfortunately,
230+ <a href =" https://domain.com" >this hyperlink is not accessible</a >
231+ </p >
232+ ```
233+
234+ Luke Walczak from Callstack [ explains how to circumvent this issue in a great
235+ post] ( https://callstack.com/blog/react-native-android-accessibility-tips/ ) .
236+ Unfortunately, this workaround is not generalizable and we will have to wait
237+ for an upstream fix.
238+
239+ :::
240+
241+ ### Enhanced Accessibility for ` <img> ` Tags
242+
243+ ` <img> ` tags have been accessible since the Foundry beta. But the accessibility
244+ props were set after the loading was complete. We have found that changing
245+ accessibility annotations depending on loading state can degrade aural
246+ experience. We have thus replaced this behavior by moving those props to the
247+ image container, to make it independent of the internal state.
248+
249+ :::info
250+ ` accessibilityRole="image" ` will be set for ` <img> ` only when either ` alt ` or
251+ ` aria-label ` attribute is present.
252+ :::
253+
254+ ### Accessible ` <h1-6> ` Tags
255+
256+ React Native has a “header” accessibility role which screen reader users depend
257+ on a lot to identify efficiently the content hierarchy of a screen. Until this
258+ release, ` react-native-render-html ` did not pass this role to heading tags.
63259
64260## Other Enhancements
65261
66262### Support for ` user-select ` CSS property
67263
264+ With the new Transient Render Engine featuring React Native prop generation, it
265+ has become very easy to pass the ` selectable ` prop to React Native ` Text `
266+ components based on the presence of ` user-select ` CSS property. Usage example:
267+
268+ ``` html
269+ <p style =" user-select : none " >
270+ This line is not selectable.<br />
271+ <span >Neither is that one.</span >
272+ </p >
273+ ```
274+
275+ :::important
276+ Please not that this is not full support. The TRE will map ` user-select: none; ` to ` selectable={false} ` and
277+ any other value to ` selectable={true} ` .
278+ :::
279+
68280### ` getNativePropsForTnode ` Function
69281
70282## Bonus: Notes on Version 6.1
@@ -77,4 +289,8 @@ should make model-based custom rendering much more prevalent. Let's take a tour!
77289
78290### ` provideEmbeddedHeaders ` Prop
79291
80- ### ` bypassAnonymousTPhrasingNodes ` Prop
292+ This is a function prop which allows consumer to generate HTTP headers for
293+ remote resources. It currently works with ` <img> ` and ` <iframe> ` tags (from the
294+ ` @native-html/iframe-plugin ` lib).
295+
296+ ### ` bypassAnonymousTPhrasingNodes ` Prop
0 commit comments