Skip to content

Commit e019a5a

Browse files
committed
feat: new enableExperimentalBRCollapsing prop to prevent extraneous spaces
fix #516
1 parent 3645211 commit e019a5a

File tree

5 files changed

+81
-7
lines changed

5 files changed

+81
-7
lines changed

packages/render-html/src/RenderHTMLConfigProvider.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export const renderHTMLConfigPropTypes: RenderHTMLConfigPropTypes = {
2626
bypassAnonymousTPhrasingNodes: PropTypes.bool,
2727
defaultTextProps: PropTypes.object,
2828
defaultViewProps: PropTypes.object,
29+
enableExperimentalBRCollapsing: PropTypes.bool,
2930
enableExperimentalGhostLinesPrevention: PropTypes.bool,
3031
enableExperimentalMarginCollapsing: PropTypes.bool,
3132
remoteErrorView: PropTypes.func,

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

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,17 +96,29 @@ describe('RenderHTML', () => {
9696
);
9797
await findByText('\u200B');
9898
});
99-
it('should use internal text renderer for <br> tags', async () => {
100-
const { findByText } = render(
99+
it('should render <br> tags to line returns when followed by text', () => {
100+
const { queryByText } = render(
101101
<RenderHTML
102102
source={{
103-
html: '<br>'
103+
html: '<br><span>Two</span>'
104104
}}
105105
debug={false}
106106
contentWidth={0}
107107
/>
108108
);
109-
await findByText('\n');
109+
expect(queryByText('\n')).toBeDefined();
110+
});
111+
it('should render <br> tags to line returns when the tag closes an inline formatting context', () => {
112+
const { queryByText } = render(
113+
<RenderHTML
114+
source={{
115+
html: 'Two<br><div></div>'
116+
}}
117+
debug={false}
118+
contentWidth={0}
119+
/>
120+
);
121+
expect(queryByText('\n')).toBeDefined();
110122
});
111123
it('should invoke renderersProps.a.onPress on <a> press', async () => {
112124
const onPress = jest.fn();
@@ -670,4 +682,32 @@ describe('RenderHTML', () => {
670682
expect(image.props.source.headers).toBe(headers);
671683
});
672684
});
685+
describe('regarding enableExperimentalBRCollapsing', () => {
686+
it('should render <br> tags to line returns when followed by text', () => {
687+
const { queryByText } = render(
688+
<RenderHTML
689+
source={{
690+
html: '<br><span>Two</span>'
691+
}}
692+
debug={false}
693+
contentWidth={0}
694+
enableExperimentalBRCollapsing
695+
/>
696+
);
697+
expect(queryByText('\n')).toBeDefined();
698+
});
699+
it('should render <br> tags to empty text when the tag closes an inline formatting context', () => {
700+
const { queryByText } = render(
701+
<RenderHTML
702+
source={{
703+
html: 'Two<br><div></div>'
704+
}}
705+
debug={false}
706+
contentWidth={0}
707+
enableExperimentalBRCollapsing
708+
/>
709+
);
710+
expect(queryByText('\n')).toBeNull();
711+
});
712+
});
673713
});

packages/render-html/src/context/defaultSharedProps.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ const defaultSharedProps: Required<RenderHTMLSharedProps> = {
2020
allowFontScaling: true
2121
},
2222
defaultViewProps: {},
23+
enableExperimentalBRCollapsing: false,
2324
enableExperimentalGhostLinesPrevention: false,
2425
enableExperimentalMarginCollapsing: false,
2526
computeEmbeddedMaxWidth: (contentWidth) => contentWidth,

packages/render-html/src/renderers/BRRenderer.tsx

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,21 @@ import React from 'react';
22
import { Text } from 'react-native';
33
import { InternalTextContentRenderer } from '../render/render-types';
44

5-
const emptyProps = {};
5+
const emptyProps = {
6+
testID: 'br'
7+
};
68

7-
const BRRenderer: InternalTextContentRenderer = function BRRenderer() {
8-
return React.createElement(Text, emptyProps, '\n');
9+
const BRRenderer: InternalTextContentRenderer = function BRRenderer({
10+
renderIndex,
11+
renderLength,
12+
sharedProps
13+
}) {
14+
// If it is the last child and BR collapsing is enabled, render nothing to
15+
// prevent inserting an undesired space and follow HTML specs.
16+
const shouldCollapse =
17+
sharedProps.enableExperimentalBRCollapsing &&
18+
renderIndex === renderLength - 1;
19+
return React.createElement(Text, emptyProps, shouldCollapse ? '' : '\n');
920
};
1021

1122
BRRenderer.isNativeInternalTextRenderer = true;

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,27 @@ export interface RenderHTMLSharedProps {
363363
*/
364364
defaultWebViewProps?: any;
365365

366+
/**
367+
* Follow closely the HTML standard and ignore `<br>` tags closing an
368+
* inline formatting context.
369+
*
370+
* @example
371+
*
372+
* ```html
373+
* <p>
374+
* Hello<br />
375+
* </p>
376+
* ```
377+
*
378+
* When this flag is set to `true`, one line is printed instead of two on
379+
* native platforms, which is the HTML-compliant behavior.
380+
*
381+
* @defaultValue false
382+
*
383+
* @remarks Recommended value is `true` on non-web platforms.
384+
*/
385+
enableExperimentalBRCollapsing?: boolean;
386+
366387
/**
367388
* React Native doesn't handle lines like we would expect on a web browser.
368389
* For example:

0 commit comments

Comments
 (0)