Skip to content

Commit 2fc1f8a

Browse files
committed
feat(HTML): rewrite how text styles are applied
This should be a much needed improvement addressing some of the oldest issues of this plugin. The new algorithm to be tried is explained [here](meliorence#102 (comment) ssuecomment-375594792)
1 parent b52ca4e commit 2fc1f8a

File tree

2 files changed

+91
-37
lines changed

2 files changed

+91
-37
lines changed

src/HTML.js

Lines changed: 30 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
11
import React, { PureComponent } from 'react';
22
import PropTypes from 'prop-types';
33
import { View, Text, ViewPropTypes, ActivityIndicator, Dimensions } from 'react-native';
4-
import { BLOCK_TAGS, TEXT_TAGS, MIXED_TAGS, IGNORED_TAGS, TEXT_TAGS_IGNORING_ASSOCIATION, STYLESETS, TextOnlyPropTypes } from './HTMLUtils';
5-
import { cssStringToRNStyle, _getElementClassStyles, cssStringToObject, cssObjectToString } from './HTMLStyles';
4+
import { cssStringToRNStyle, _getElementClassStyles, cssStringToObject, cssObjectToString, computeTextStyles } from './HTMLStyles';
5+
import {
6+
BLOCK_TAGS,
7+
TEXT_TAGS,
8+
MIXED_TAGS,
9+
IGNORED_TAGS,
10+
TEXT_TAGS_IGNORING_ASSOCIATION,
11+
STYLESETS,
12+
TextOnlyPropTypes
13+
} from './HTMLUtils';
614
import { generateDefaultBlockStyles, generateDefaultTextStyles } from './HTMLDefaultStyles';
715
import htmlparser2 from 'htmlparser2';
816
import * as HTMLRenderers from './HTMLRenderers';
@@ -149,36 +157,6 @@ export default class HTML extends PureComponent {
149157
this.defaultTextStyles = generateDefaultTextStyles(baseFontStyle.fontSize || 14);
150158
}
151159

152-
filterBaseFontStyles (element, classStyles, props = this.props) {
153-
const { tagsStyles, baseFontStyle } = props;
154-
const { tagName, parentTag, parent, attribs } = element;
155-
const styles = Object.keys(baseFontStyle);
156-
let appliedStyles = {};
157-
158-
for (let i = 0; i < styles.length; i++) {
159-
const styleAttribute = styles[i];
160-
const tagToCheck = tagName === 'rawtext' ? parentTag : tagName;
161-
const styleAttributeWithCSSDashes = styleAttribute.replace(/[A-Z]/, (match) => { return `-${match.toLowerCase()}`; });
162-
const overridenFromStyle = attribs && attribs.style && attribs.style.search(styleAttributeWithCSSDashes) !== -1;
163-
const overridenFromParentStyle = parent && parent.attribs && parent.attribs.style && parent.attribs.style.search(styleAttributeWithCSSDashes) !== -1;
164-
165-
const overridenFromTagStyle = tagToCheck && tagsStyles[tagToCheck] && tagsStyles[tagToCheck][styleAttribute];
166-
const overridenFromParentTagStyle = parentTag && tagsStyles[parentTag] && tagsStyles[parentTag][styleAttribute];
167-
168-
const overridenFromClassStyles = classStyles && classStyles[styleAttribute];
169-
const overridenFromDefaultStyles = this.defaultTextStyles[tagToCheck] && this.defaultTextStyles[tagToCheck][styleAttribute];
170-
171-
const notOverriden = !overridenFromStyle && !overridenFromParentStyle &&
172-
!overridenFromTagStyle && !overridenFromParentTagStyle &&
173-
!overridenFromClassStyles && !overridenFromDefaultStyles;
174-
175-
if (notOverriden) {
176-
appliedStyles[styleAttribute] = baseFontStyle[styleAttribute];
177-
}
178-
}
179-
return appliedStyles;
180-
}
181-
182160
/**
183161
* Loop on children and return whether if their parent needs to be a <View>
184162
* @param {any} children
@@ -403,7 +381,7 @@ export default class HTML extends PureComponent {
403381
* @memberof HTML
404382
*/
405383
renderRNElements (RNElements, parentWrapper = 'root', parentIndex = 0, props = this.props) {
406-
const { tagsStyles, classesStyles, emSize, ptSize, ignoredStyles, allowedStyles } = props;
384+
const { tagsStyles, classesStyles, emSize, ptSize, ignoredStyles, allowedStyles, baseFontStyle } = props;
407385
return RNElements && RNElements.length ? RNElements.map((element, index) => {
408386
const { attribs, data, tagName, parentTag, children, nodeIndex, wrapper } = element;
409387
const Wrapper = wrapper === 'Text' ? Text : View;
@@ -449,9 +427,23 @@ export default class HTML extends PureComponent {
449427
}
450428

451429
const classStyles = _getElementClassStyles(attribs, classesStyles);
452-
const textElementStyles = this.filterBaseFontStyles(element, classStyles, props);
453430
const textElement = data ?
454-
<Text style={textElementStyles}>{ data }</Text> :
431+
<Text
432+
style={computeTextStyles(
433+
element,
434+
{
435+
defaultTextStyles: this.defaultTextStyles,
436+
tagsStyles,
437+
classesStyles,
438+
baseFontStyle,
439+
emSize,
440+
ptSize,
441+
ignoredStyles,
442+
allowedStyles
443+
})}
444+
>
445+
{ data }
446+
</Text> :
455447
false;
456448

457449
const style = [
@@ -463,7 +455,9 @@ export default class HTML extends PureComponent {
463455
.filter((s) => s !== undefined);
464456

465457
const extraProps = {};
466-
if (Wrapper === Text) extraProps.selectable = this.props.textSelectable;
458+
if (Wrapper === Text) {
459+
extraProps.selectable = this.props.textSelectable;
460+
}
467461
return (
468462
<Wrapper key={key} style={style} {...extraProps}>
469463
{ textElement }

src/HTMLStyles.js

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,66 @@ export function _constructStyles ({ tagName, htmlAttribs, passProps, additionalS
6363
return style.filter((style) => style !== undefined);
6464
}
6565

66+
/**
67+
* Computes the styles of a text node
68+
* @export
69+
* @param {any} element parsed DOM node of text
70+
* @param {any} passProps set of props from the HTML component
71+
* @returns {object} react-native styles
72+
*/
73+
export function computeTextStyles (element, passProps) {
74+
let finalStyle = {};
75+
76+
// Construct an array with the styles of each level of the text node, ie :
77+
// [element, parent1, parent2, parent3...]
78+
const parentStyles = _recursivelyComputeParentTextStyles(element, passProps);
79+
80+
// Only merge the keys that aren't yet applied to the final object. ie:
81+
// if fontSize is already set in the first iteration, ignore the fontSize that
82+
// we got from the 3rd iteration because of a class for instance, hence
83+
// respecting the proper style inheritance
84+
parentStyles.forEach((styles) => {
85+
Object.keys(styles).forEach((styleKey) => {
86+
const styleValue = styles[styleKey];
87+
if (!finalStyle[styleKey]) {
88+
finalStyle[styleKey] = styleValue;
89+
}
90+
});
91+
});
92+
93+
// Finally, try to add the baseFontStyle values to add pontentially missing
94+
// styles to each text node
95+
return { ...passProps.baseFontStyle, ...finalStyle };
96+
}
97+
98+
function _recursivelyComputeParentTextStyles (element, passProps, styles = []) {
99+
const { attribs, name } = element;
100+
const { classesStyles, tagsStyles, defaultTextStyles } = passProps;
101+
102+
// Construct every style for this node
103+
const HTMLAttribsStyles = attribs && attribs.style ? cssStringToRNStyle(attribs.style, STYLESETS.TEXT, passProps) : {};
104+
const classStyles = _getElementClassStyles(attribs, classesStyles);
105+
const userTagStyles = tagsStyles[name];
106+
const defaultTagStyles = defaultTextStyles[name];
107+
108+
// Merge those according to their priority level
109+
const mergedStyles = {
110+
...defaultTagStyles,
111+
...userTagStyles,
112+
...classStyles,
113+
...HTMLAttribsStyles
114+
};
115+
116+
styles.push(mergedStyles);
117+
118+
if (element.parent) {
119+
// Keep looping recursively if this node has parents
120+
return _recursivelyComputeParentTextStyles(element.parent, passProps, styles);
121+
} else {
122+
return styles;
123+
}
124+
}
125+
66126
/**
67127
* Creates a set of style from an array of classes asosciated to a node.
68128
* @export
@@ -101,7 +161,7 @@ export function _getElementCSSClasses (htmlAttribs) {
101161
* @param {object} { parentTag, emSize, ignoredStyles }
102162
* @returns {object}
103163
*/
104-
function cssToRNStyle (css, styleset, { parentTag, emSize, ptSize, ignoredStyles, allowedStyles }) {
164+
function cssToRNStyle (css, styleset, { emSize, ptSize, ignoredStyles, allowedStyles }) {
105165
const styleProps = stylePropTypes[styleset];
106166
return Object.keys(css)
107167
.filter((key) => allowedStyles ? allowedStyles.indexOf(key) !== -1 : true)

0 commit comments

Comments
 (0)