Skip to content

Commit

Permalink
feat: new renderers API
Browse files Browse the repository at this point in the history
The new renderer API is based on the transient render engine available
at @native-html/transient-render-engine.

This API changes the nature of renderers, which are now React component
instead of render functions taking 4 arguments. Most notably:

- The first argument has been replaced with the "tnode" prop;
- The second argument has been replaced with "children" props;
- The third argument has been replaced with "style" prop;
- The fourth argument is available with "useSharedProps" hook.

In addition, the "TDefaultRenderer" prop is a component to render the
passed tnode. Consumers can also take advantage of the TChildrenRenderer
and TNodeChildrenRenderer exported components to customize the rendering
of the component, while preserving the rendering logic of this library.

Moreover, renderers must declare a "model" static field to specify the
HTMLElementModel corresponding to the tag to render.
  • Loading branch information
jsamr committed Jun 4, 2021
1 parent 1114166 commit 2547cba
Show file tree
Hide file tree
Showing 60 changed files with 1,721 additions and 986 deletions.
37 changes: 37 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,43 @@ will probably be very useful for other users.

## Creating custom renderers

If you want to customize the rendering logic, there are multiple options to
consider, depending on particularities of the tag you want to render. The best,
and easiest option is to use **transient renderers**. If you need more customization
logic, you can use **plain renderers**.

### Transient Render Tree Tampering

Transient renderers don't provide you with the option of rendering custom
tags. Instead, customize the props and styles of default renderers,
depending on the transient render nodes they receive as input.

The big advantage is that transient renderers receive all the benefits from the
transient render engine, such as style inheritance, hoisting... etc.

### Plain renderers

Plain renderers declaration depends on the model of the tag they render. If the
tag is a _textual_ tag, such as `em`, `span`, ...etc, you will have to declare
a `TextualRenderer`. On the other hand, if the tag is a _block_ tag (embedded
such as `img`, grouping such as `div`, `ol`, `section`, `menu`), you will have
to declare a `BlockRenderer`. Some tags can be both such as `a`, `del` and
`ins` tags. In that case, you must declare a `MixedRenderer`, which will have
to handle both situations. When you declare a renderer for a custom tag, the
model by which hoisting will apply will be determined by the renderer type you
chose.

Each of these renderer can be _opaque_, provided the option. Opaque renderers
have access to the DOM tree (not to be conflated with the render tree!) of the
children, or the raw html. This is especially useful for special markup such as
**SVG** and **MathML**.

Some special tags have extensible renderers which you can inherit to benefit
from their layout model. Examples are `ImageRenderer`, `OrderedListRenderer`,
̀`UnorderedListRenderer` (see "extending renderers" below).

## Creating custom renderers

This is very useful if you want to make some very specific styling of your HTML content, or even implement custom HTML tags.

### Custom HTML tags
Expand Down
43 changes: 32 additions & 11 deletions demo/components/Snippet.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useCallback } from 'react';
import { ScrollView, Linking, useWindowDimensions } from 'react-native';
import { ScrollView, Linking, useWindowDimensions, View } from 'react-native';
import RenderHTML, { RenderHTMLProps } from 'react-native-render-html';
import LegacyHTML from 'rnrh-legacy';
import Constants from 'expo-constants';
Expand All @@ -8,6 +8,7 @@ import snippets, { SnippetId } from '../snippets';
import { useSetHTMLForSnippet, useSetTTreeForSnippet } from '../state/store';
import { useComponentColors } from '../state/ThemeProvider';
import DisplayLoading from './DisplayLoading';
import Text from './Text';

const DEFAULT_PROPS: Pick<
RenderHTMLProps,
Expand Down Expand Up @@ -45,28 +46,28 @@ const CONTAINER_PADDING = 10;

const Snippet = React.memo(
({
exampleId,
snippetId,
useLegacy = false
}: {
exampleId: SnippetId;
snippetId: SnippetId;
useLegacy: boolean;
}) => {
const { width: contentWidth } = useWindowDimensions();
const setHtmlForSnippet = useSetHTMLForSnippet();
const setTTreeForSnippet = useSetTTreeForSnippet();
const setHTML = useCallback(
(html: string) => {
setHtmlForSnippet(exampleId, html);
setHtmlForSnippet(snippetId, html);
},
[setHtmlForSnippet, exampleId]
[setHtmlForSnippet, snippetId]
);
const setTTree = useCallback(
(ttree: TNode) => {
setTTreeForSnippet(exampleId, ttree);
setTTreeForSnippet(snippetId, ttree);
},
[setTTreeForSnippet, exampleId]
[setTTreeForSnippet, snippetId]
);
const additionalProps = snippets[exampleId].props || {};
const additionalProps = snippets[snippetId].props || {};
const {
html: { color, backgroundColor, border }
} = useComponentColors();
Expand All @@ -78,10 +79,9 @@ const Snippet = React.memo(
const sharedProps = {
...DEFAULT_PROPS,
contentWidth: contentWidth - CONTAINER_PADDING * 2,
html: snippets[exampleId].html,
html: snippets[snippetId].html,
...(additionalProps as any),
textSelectable: true,
renderers: {}
textSelectable: true
};
const mergedTagsStyles = {
...sharedProps.tagsStyles,
Expand All @@ -98,6 +98,27 @@ const Snippet = React.memo(
() => [...Constants.systemFonts, 'space-mono'],
[]
);

if (
(snippetId === 'customRenderers' || snippetId === 'customTags') &&
useLegacy
) {
return (
<View
style={{
alignItems: 'center',
justifyContent: 'center',
marginHorizontal: 30,
flexGrow: 1
}}>
<Text
style={{ textAlign: 'center', fontSize: 20, fontStyle: 'italic' }}>
Legacy HTML component is not available for this snippet.
</Text>
</View>
);
}

const renderHtml = useLegacy ? (
<LegacyHTML
{...sharedProps}
Expand Down
4 changes: 1 addition & 3 deletions demo/screens/HomeDrawerScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
import { StackScreenProps } from '@react-navigation/stack';
import VersionDisplay from '../components/VersionDisplay';
import SnippetScreen from './SnippetScreen';
import snippets, { SnippetId } from '../snippets';
import snippets, { devSelectedSnippet, SnippetId } from '../snippets';
import { Platform } from 'react-native';
import DrawerHeader from '../components/DrawerHeader';
import { useComponentColors } from '../state/ThemeProvider';
Expand All @@ -18,8 +18,6 @@ import { useSetSelectedSnippetId } from '../state/store';

const Drawer = createDrawerNavigator<Record<keyof typeof snippets, any>>();

const devSelectedSnippet = 'test';

function CustomDrawerContent(props: DrawerContentComponentProps<any>) {
const {
drawer: { backgroundColor, activeTintColor, activeBackgroundColor }
Expand Down
2 changes: 1 addition & 1 deletion demo/screens/SnippetScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ import { useLegacyMode } from '../state/store';
export default function SnippetScreen({ route }: DrawerScreenProps<any>) {
const legacyMode = useLegacyMode();
const { snippetId } = route.params as any;
return <Snippet useLegacy={legacyMode} exampleId={snippetId} />;
return <Snippet useLegacy={legacyMode} snippetId={snippetId} />;
}
Loading

0 comments on commit 2547cba

Please sign in to comment.