Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion packages/.gitignore

This file was deleted.

2 changes: 1 addition & 1 deletion packages/render-html/src/TNodeRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ const TNodeRenderer = memo(function MemoizedTNodeRenderer(
? renderBlockContent
: renderTextualContent;
return Renderer === null
? renderFn(assembledProps)
? renderFn(assembledProps as any)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@DorianMazur We prefer as never to as any (well we prefer none). This is immediate technical debt, please open a ticket and add a reference to this ticket in a comment to this line.

: React.createElement(Renderer as any, assembledProps);
});

Expand Down
87 changes: 87 additions & 0 deletions packages/render-html/src/render/RenderRegistry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { TNode } from '@native-html/transient-render-engine';
import { ComponentType } from 'react';
import { CustomRendererProps } from '..';
import lookupRecord from '../helpers/lookupRecord';
import BRRenderer from '../renderers/BRRenderer';
import WBRRenderer from '../renderers/WBRRenderer';
import {
CustomRenderer,
HTMLElementModelRecord,
InternalRenderer
} from '../shared-types';
import internalRenderers from './internalRenderers';
import {
CustomTagRendererRecord,
InternalTextContentRenderer
} from './render-types';

const internalTextRenderers: Record<string, InternalTextContentRenderer> = {
br: BRRenderer,
wbr: WBRRenderer
};

export interface RendererConfig<T extends TNode> {
Custom: CustomRenderer<T> | null;
Default: InternalRenderer<T> | null;
}

export default class RenderRegistry {
constructor(
customRenderers: CustomTagRendererRecord = {},
elementModels: HTMLElementModelRecord
) {
this.customRenderers = customRenderers;
this.elementModels = elementModels;
}

private readonly customRenderers: CustomTagRendererRecord = {};
private readonly elementModels: HTMLElementModelRecord;

private getCustomRendererForTNode<T extends TNode>(
tnode: T
): ComponentType<CustomRendererProps<T>> | null {
if (tnode.tagName! in this.customRenderers) {
const renderer = this.customRenderers[tnode.tagName!];
/* istanbul ignore next */
if (typeof __DEV__ === 'boolean' && __DEV__) {
// In DEV, check for discrepancies.
const elementModel = this.elementModels[tnode.tagName!];
if (!elementModel) {
console.warn(
`You are attempting to render a ${tnode.tagName!} tag but you didn't provide an HTMLElementModel. Make sure you register a model for this tag in "customHTMLElementModels" prop. `
);
} else if (!tnode.matchContentModel(elementModel.contentModel)) {
console.warn(
`You are attempting to render "${tnode.tagName}" of type "${tnode.displayName}", but the registered renderer is of content model type ${elementModel?.contentModel} which is incompatible with "${tnode.displayName}".`
);
}
}
return renderer as any;
}
return null;
}

private getDefaultRendererForTNode<T extends TNode>(
tnode: T
): InternalRenderer<T> | null {
if (tnode.tagName! in internalRenderers) {
//@ts-expect-error we know that the tagName is in the map
return internalRenderers[tnode.tagName!];
}
return null;
}

getInternalTextRenderer(tagName: string | null) {
if (lookupRecord(internalTextRenderers, tagName)) {
return internalTextRenderers[tagName];
}
return null;
}

getRendererConfigForTNode<T extends TNode>(tnode: T): RendererConfig<T> {
return {
Custom: this.getCustomRendererForTNode(tnode),
Default: this.getDefaultRendererForTNode(tnode)
};
}
}
13 changes: 13 additions & 0 deletions packages/render-html/src/render/internalRenderers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import ARenderer from '../renderers/ARenderer';
import IMGRenderer from '../renderers/IMGRenderer';
import OLRenderer from '../renderers/OLRenderer';
import ULRenderer from '../renderers/ULRenderer';

const internalRenderers = {
img: IMGRenderer,
ul: ULRenderer,
ol: OLRenderer,
a: ARenderer
};

export default internalRenderers;
53 changes: 53 additions & 0 deletions packages/render-html/src/render/render-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { TBlock, TPhrasing, TText } from '@native-html/transient-render-engine';
import { ComponentType } from 'react';
import { TNodeSubRendererProps } from '../internal-types';
import { CustomRenderer, InternalRenderer } from '../shared-types';

/**
* Special internal renderers for non-printable text (wbr, br).
*/
export type InternalTextContentRenderer = ComponentType<
TNodeSubRendererProps<TText>
> & {
isNativeInternalTextRenderer: true;
};

/**
* Internal renderer for tags with a **block** content model.
*/
export type InternalBlockRenderer = InternalRenderer<TBlock>;

/**
* Internal renderer for tags with a **mixed** content model.
*/
export type InternalMixedRenderer = InternalRenderer<
TBlock | TPhrasing | TText
>;

/**
* Internal renderer for tags with a **textual** content model.
*/
export type InternalTextualRenderer = InternalRenderer<TPhrasing | TText>;

/**
* Block renderers can only render tnodes of type TBlock.
*/
export type CustomBlockRenderer = CustomRenderer<TBlock>;

/**
* Textual renderers can render tnodes of type TText or TPhrasing.
*/
export type CustomTextualRenderer = CustomRenderer<TText | TPhrasing>;

/**
* Mixed renderers can can render tnodes of type TText, TPhrasing or TBlock.
*/
export type CustomMixedRenderer = CustomRenderer<TBlock | TPhrasing | TText>;

/**
* A record of custom renderers.
*/
export type CustomTagRendererRecord = Record<
string,
CustomBlockRenderer | CustomTextualRenderer | CustomMixedRenderer
>;