Skip to content

Commit 8dacea7

Browse files
committed
refactor: enhance type extensibility for EmbeddedWithHeadersParamsMap
1 parent 41fca8f commit 8dacea7

File tree

4 files changed

+197
-12
lines changed

4 files changed

+197
-12
lines changed

packages/render-html/src/TBlockRenderer.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,18 @@ export const TDefaultBlockRenderer: TDefaultRenderer<TBlock> = ({
1313
style,
1414
onPress,
1515
viewProps,
16+
nativeProps,
1617
propsForChildren
1718
}) => {
1819
const TNodeChildrenRenderer = useTNodeChildrenRenderer();
1920
const children = overridingChildren ?? (
2021
<TNodeChildrenRenderer tnode={tnode} propsForChildren={propsForChildren} />
2122
);
2223
const commonProps = {
24+
...tnode.getReactNativeProps()?.view,
25+
...nativeProps,
2326
...viewProps,
24-
style: viewProps?.style ? [style, viewProps.style] : style,
27+
style: [style, nativeProps?.style, viewProps.style],
2528
testID: tnode.tagName
2629
};
2730
if (typeof onPress === 'function') {

packages/render-html/src/TDefaultTextualRenderer.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,15 @@ const TDefaultTextualRenderer: TDefaultRenderer<TPhrasing | TText> = ({
88
style,
99
children,
1010
textProps,
11+
nativeProps,
1112
onPress
1213
}) => {
13-
const resolvedStyles = textProps?.style ? [style, textProps.style] : style;
14+
const resolvedStyles = [style, nativeProps?.style, textProps.style];
1415
return React.createElement(
1516
Text,
1617
{
18+
...tnode.getReactNativeProps()?.text,
19+
...nativeProps,
1720
...textProps,
1821
onPress,
1922
style: resolvedStyles,

packages/render-html/src/__tests__/component.render-html.test.tsx

Lines changed: 158 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { useRendererProps } from '../context/RenderersPropsProvider';
1616
import TNodeChildrenRenderer from '../TNodeChildrenRenderer';
1717
import OLElement from '../elements/OLElement';
1818
import ULElement from '../elements/ULElement';
19+
import { HTMLElementModelRecord } from '../shared-types';
1920

2021
describe('RenderHTML', () => {
2122
it('should render without error when providing a source', () => {
@@ -179,7 +180,7 @@ describe('RenderHTML', () => {
179180
/>
180181
);
181182
const span = getByTestId('span');
182-
expect(span.props.style).toMatchObject(tagsStyles.span);
183+
expect(StyleSheet.flatten(span.props.style)).toMatchObject(tagsStyles.span);
183184
});
184185
describe('regarding onTTreeChange prop', () => {
185186
const onTTreeChange = jest.fn();
@@ -360,6 +361,162 @@ describe('RenderHTML', () => {
360361
const em = UNSAFE_getByType(EmRenderer);
361362
expect(em.props.propsFromParent.test).toBeUndefined();
362363
});
364+
it('should apply `viewProps` to TBlock renderers', () => {
365+
const DivRenderer: CustomTextualRenderer = ({
366+
TDefaultRenderer,
367+
...props
368+
}) => <TDefaultRenderer {...props} viewProps={{ collapsable: false }} />;
369+
const { getByTestId } = render(
370+
<RenderHTML
371+
source={{
372+
html: '<div>test</div>'
373+
}}
374+
renderers={{ div: DivRenderer }}
375+
debug={false}
376+
contentWidth={100}
377+
/>
378+
);
379+
const div = getByTestId('div');
380+
expect(div.props.collapsable).toBe(false);
381+
});
382+
it('should apply `textProps` to TPhrasing renderers', () => {
383+
const SpanRenderer: CustomTextualRenderer = ({
384+
TDefaultRenderer,
385+
...props
386+
}) => (
387+
<TDefaultRenderer
388+
{...props}
389+
textProps={{ adjustsFontSizeToFit: true }}
390+
/>
391+
);
392+
const { getByTestId } = render(
393+
<RenderHTML
394+
source={{
395+
html: '<span>foo<b>bar</b></span>'
396+
}}
397+
renderers={{ span: SpanRenderer }}
398+
debug={false}
399+
contentWidth={100}
400+
/>
401+
);
402+
const span = getByTestId('span');
403+
expect(span.props.adjustsFontSizeToFit).toBe(true);
404+
});
405+
it('should apply `textProps` to TText renderers', () => {
406+
const SpanRenderer: CustomTextualRenderer = ({
407+
TDefaultRenderer,
408+
...props
409+
}) => (
410+
<TDefaultRenderer
411+
{...props}
412+
textProps={{ adjustsFontSizeToFit: true }}
413+
/>
414+
);
415+
const { getByTestId } = render(
416+
<RenderHTML
417+
source={{
418+
html: '<span>foo</span>'
419+
}}
420+
renderers={{ span: SpanRenderer }}
421+
debug={false}
422+
contentWidth={100}
423+
/>
424+
);
425+
const span = getByTestId('span');
426+
expect(span.props.adjustsFontSizeToFit).toBe(true);
427+
});
428+
it('should apply `props`', () => {
429+
const SpanRenderer: CustomTextualRenderer = ({
430+
TDefaultRenderer,
431+
...props
432+
}) => (
433+
<TDefaultRenderer
434+
{...props}
435+
nativeProps={{ accessibilityRole: 'adjustable' }}
436+
/>
437+
);
438+
const { getByTestId } = render(
439+
<RenderHTML
440+
source={{
441+
html: '<span>foo</span>'
442+
}}
443+
renderers={{ span: SpanRenderer }}
444+
debug={false}
445+
contentWidth={100}
446+
/>
447+
);
448+
const span = getByTestId('span');
449+
expect(span.props.accessibilityRole).toBe('adjustable');
450+
});
451+
it('should apply `tnode.getReactNativeProps()` to TPhrasing renderers', () => {
452+
const customHTMLElementModels: HTMLElementModelRecord = {
453+
span: defaultHTMLElementModels.span.extend({
454+
reactNativeProps: {
455+
native: {
456+
accessibilityRole: 'adjustable'
457+
}
458+
}
459+
})
460+
};
461+
const { getByTestId } = render(
462+
<RenderHTML
463+
source={{
464+
html: '<span>foo<b>bar</br></span>'
465+
}}
466+
customHTMLElementModels={customHTMLElementModels}
467+
debug={false}
468+
contentWidth={100}
469+
/>
470+
);
471+
const span = getByTestId('span');
472+
expect(span.props.accessibilityRole).toBe('adjustable');
473+
});
474+
it('should apply `tnode.getReactNativeProps()` to TText renderers', () => {
475+
const customHTMLElementModels: HTMLElementModelRecord = {
476+
span: defaultHTMLElementModels.span.extend({
477+
reactNativeProps: {
478+
native: {
479+
accessibilityRole: 'adjustable'
480+
}
481+
}
482+
})
483+
};
484+
const { getByTestId } = render(
485+
<RenderHTML
486+
source={{
487+
html: '<span>foo</span>'
488+
}}
489+
customHTMLElementModels={customHTMLElementModels}
490+
debug={false}
491+
contentWidth={100}
492+
/>
493+
);
494+
const span = getByTestId('span');
495+
expect(span.props.accessibilityRole).toBe('adjustable');
496+
});
497+
it('should apply `tnode.getReactNativeProps()` to TBlock renderers', () => {
498+
const customHTMLElementModels: HTMLElementModelRecord = {
499+
div: defaultHTMLElementModels.span.extend({
500+
reactNativeProps: {
501+
native: {
502+
accessibilityRole: 'adjustable'
503+
}
504+
}
505+
})
506+
};
507+
const { getByTestId } = render(
508+
<RenderHTML
509+
source={{
510+
html: '<div>test</div>'
511+
}}
512+
customHTMLElementModels={customHTMLElementModels}
513+
debug={false}
514+
contentWidth={100}
515+
/>
516+
);
517+
const div = getByTestId('div');
518+
expect(div.props.accessibilityRole).toBe('adjustable');
519+
});
363520
});
364521
describe('regarding TNodeRenderer', () => {
365522
describe('TBlockRenderer', () => {

packages/render-html/src/shared-types.ts

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import type {
1515
DocumentContext as TREDocumentContext,
1616
DomVisitorCallbacks,
1717
Element,
18+
EmbeddedTagNames,
1819
HTMLContentModel,
1920
HTMLElementModel,
2021
MixedStyleDeclaration,
@@ -24,6 +25,7 @@ import type {
2425
Node,
2526
NodeWithChildren,
2627
SetMarkersForTNode,
28+
StylessReactNativeProps,
2729
TDocument,
2830
TNode,
2931
TPhrasing,
@@ -232,7 +234,8 @@ export interface RenderHTMLPassedProps {
232234
* A map which defines the type of parameters passed as third argument
233235
* of {@link EmbeddedHeadersProvider}.
234236
*/
235-
export interface EmbeddedWithHeadersParamsMap {
237+
export interface EmbeddedWithHeadersParamsMap
238+
extends Record<EmbeddedWithHeadersTagName, Record<string, unknown>> {
236239
img: {
237240
/**
238241
* The print height of the image in DPI, if it can be determined beforehand
@@ -248,16 +251,19 @@ export interface EmbeddedWithHeadersParamsMap {
248251
}
249252

250253
/**
251-
* Tag names eligible for headers providing.
254+
* Tag names eligible for headers provision.
252255
*/
253-
export type EmbeddedWithHeadersTagName = keyof EmbeddedWithHeadersParamsMap;
256+
export type EmbeddedWithHeadersTagName = Exclude<
257+
EmbeddedTagNames,
258+
'svg' | 'canvas' | 'math'
259+
>;
254260

255261
/**
256262
* A function to provide headers to a peculiar embedded element.
257263
*/
258264
export type EmbeddedHeadersProvider = <T extends EmbeddedWithHeadersTagName>(
259265
uri: string,
260-
tagName: T,
266+
tagName: string,
261267
params: EmbeddedWithHeadersParamsMap[T]
262268
) => Record<string, string> | null | void;
263269

@@ -1045,31 +1051,47 @@ export interface TNodeRendererProps<T extends TNode> {
10451051
*/
10461052
export interface RendererBaseProps<T extends TNode>
10471053
extends TNodeRendererProps<T> {
1054+
/**
1055+
* Props passed to the underlying React Native element, either `Text` or
1056+
* `View`. See also {@link RendererBaseProps.textProps} and
1057+
* {@link RendererBaseProps.viewProps}.
1058+
*
1059+
* @remarks The `prop.style` property will have a greater specificity
1060+
* than computed styles for this {@link TNode}. E.g. `style={[computedStyle,
1061+
* nativeProps.style, viewProps.style]}`.
1062+
*
1063+
*/
1064+
1065+
nativeProps?: StylessReactNativeProps & { style?: StyleProp<ViewStyle> };
10481066
/**
10491067
* Any default renderer should be able to handle press.
10501068
*/
10511069
onPress?: (e: GestureResponderEvent) => void;
10521070

10531071
/**
1054-
* Props for Text-based renderers.
1072+
* Props passed to the underlying `Text` element (`type` must be 'text'). See
1073+
* also {@link RendererBaseProps.nativeProps} and
1074+
* {@link RendererBaseProps.viewProps}.
10551075
*
10561076
* @remarks The `textProps.style` property will have a greater specificity than
10571077
* computed styles for this {@link TNode}. E.g. `style={[computedStyle,
1058-
* textProps.style]}`.
1078+
* nativeProps.style, textProps.style]}`.
10591079
*/
10601080
textProps: TextProps;
10611081

10621082
/**
1063-
* Is is a text-based or view-based renderer?
1083+
* Is the underlying component `Text` or `View`?
10641084
*/
10651085
type: 'text' | 'block';
10661086

10671087
/**
1068-
* Props for View-based renderers.
1088+
* Props passed to the underlying `View` element (`type` must be 'view'). See
1089+
* also {@link RendererBaseProps.nativeProps} and
1090+
* {@link RendererBaseProps.textProps}.
10691091
*
10701092
* @remarks The `viewProps.style` property will have a greater specificity than
10711093
* computed styles for this {@link TNode}. E.g. `style={[computedStyle,
1072-
* viewProps.style]}`.
1094+
* nativeProps.style, viewProps.style]}`.
10731095
*/
10741096
viewProps: ViewProps;
10751097
}

0 commit comments

Comments
 (0)