mirror of
https://github.com/zhigang1992/react-native-web.git
synced 2026-01-12 22:51:09 +08:00
[fix] i18n styles
1. Fix auto-flipping of styles The StyleRegistry didn't account for LTR/RTL when caching the results of style resolution. The 'writingDirection' style is no longer flipped; no clear use case for it. 2. Remove experimental '$noI18n' style prop suffix This feature is essentially unused, and less likely to be used with the introduction of 'dir=auto' on 'Text'. Removing also marginally improves render performance.
This commit is contained in:
@@ -68,22 +68,19 @@ Lets the user select the text.
|
||||
+ `fontWeight`
|
||||
+ `letterSpacing`
|
||||
+ `lineHeight`
|
||||
+ `textAlign`‡
|
||||
+ `textAlign`
|
||||
+ `textAlignVertical`
|
||||
+ `textDecorationLine`
|
||||
+ `textOverflow`
|
||||
+ `textRendering`
|
||||
+ `textShadowColor`
|
||||
+ `textShadowOffset`‡
|
||||
+ `textShadowOffset`
|
||||
+ `textShadowRadius`
|
||||
+ `textTransform`
|
||||
+ `unicodeBidi`
|
||||
+ `whiteSpace`
|
||||
+ `wordWrap`
|
||||
+ `writingDirection`‡
|
||||
|
||||
‡ This property can be suffixed with `$noI18n` to prevent automatic
|
||||
bidi-flipping in RTL mode. This is only supported if `Platform.OS === 'web'`.
|
||||
+ `writingDirection`
|
||||
|
||||
**testID**: string
|
||||
|
||||
|
||||
@@ -119,22 +119,22 @@ from `style`.
|
||||
+ `borderColor` (single value)
|
||||
+ `borderTopColor`
|
||||
+ `borderBottomColor`
|
||||
+ `borderRightColor`‡
|
||||
+ `borderLeftColor`‡
|
||||
+ `borderRightColor`
|
||||
+ `borderLeftColor`
|
||||
+ `borderRadius` (single value)
|
||||
+ `borderTopLeftRadius`‡
|
||||
+ `borderTopRightRadius`‡
|
||||
+ `borderBottomLeftRadius`‡
|
||||
+ `borderBottomRightRadius`‡
|
||||
+ `borderTopLeftRadius`
|
||||
+ `borderTopRightRadius`
|
||||
+ `borderBottomLeftRadius`
|
||||
+ `borderBottomRightRadius`
|
||||
+ `borderStyle` (single value)
|
||||
+ `borderTopStyle`
|
||||
+ `borderRightStyle`‡
|
||||
+ `borderRightStyle`
|
||||
+ `borderBottomStyle`
|
||||
+ `borderLeftStyle`‡
|
||||
+ `borderLeftStyle`
|
||||
+ `borderWidth` (single value)
|
||||
+ `borderBottomWidth`
|
||||
+ `borderLeftWidth`‡
|
||||
+ `borderRightWidth`‡
|
||||
+ `borderLeftWidth`
|
||||
+ `borderRightWidth`
|
||||
+ `borderTopWidth`
|
||||
+ `bottom`
|
||||
+ `boxShadow`
|
||||
@@ -148,12 +148,12 @@ from `style`.
|
||||
+ `flexWrap`
|
||||
+ `height`
|
||||
+ `justifyContent`
|
||||
+ `left`‡
|
||||
+ `left`
|
||||
+ `margin` (single value)
|
||||
+ `marginBottom`
|
||||
+ `marginHorizontal`
|
||||
+ `marginLeft`‡
|
||||
+ `marginRight`‡
|
||||
+ `marginLeft`
|
||||
+ `marginRight`
|
||||
+ `marginTop`
|
||||
+ `marginVertical`
|
||||
+ `maxHeight`
|
||||
@@ -168,14 +168,14 @@ from `style`.
|
||||
+ `padding` (single value)
|
||||
+ `paddingBottom`
|
||||
+ `paddingHorizontal`
|
||||
+ `paddingLeft`‡
|
||||
+ `paddingRight`‡
|
||||
+ `paddingLeft`
|
||||
+ `paddingRight`
|
||||
+ `paddingTop`
|
||||
+ `paddingVertical`
|
||||
+ `perspective`
|
||||
+ `perspectiveOrigin`
|
||||
+ `position`
|
||||
+ `right`‡
|
||||
+ `right`
|
||||
+ `top`
|
||||
+ `transform`
|
||||
+ `transformOrigin`
|
||||
@@ -188,9 +188,6 @@ from `style`.
|
||||
+ `width`
|
||||
+ `zIndex`
|
||||
|
||||
‡ This property can be suffixed with `$noI18n` to prevent automatic
|
||||
bidi-flipping in RTL mode. This is only supported if `Platform.OS === 'web'`.
|
||||
|
||||
Default:
|
||||
|
||||
```js
|
||||
|
||||
@@ -4,11 +4,6 @@ To support right-to-left languages, application layout can be automatically
|
||||
flipped from LTR to RTL. The `I18nManager` API can be used to help with more
|
||||
fine-grained control and testing of RTL layouts.
|
||||
|
||||
React Native for Web provides an experimental feature to support "true left"
|
||||
and "true right" styles. For example, `left` will be flipped to `right` in RTL
|
||||
mode, but `left$noI18n` will not. More information is available in the `Text`
|
||||
and `View` documentation.
|
||||
|
||||
## Working with icons and images
|
||||
|
||||
Icons and images that must match the LTR or RTL layout of the app need to be manually flipped.
|
||||
|
||||
@@ -14,14 +14,22 @@ class I18nManagerExample extends Component {
|
||||
LTR/RTL layout example!
|
||||
</Text>
|
||||
<Text style={styles.text}>
|
||||
This is sample text. The writing direction can be changed by pressing the button below.
|
||||
</Text>
|
||||
<Text style={[ styles.text, styles.ltrText ]}>
|
||||
This is text that will always display LTR.
|
||||
The writing direction of text is automatically determined by the browser, independent of the global writing direction of the app.
|
||||
</Text>
|
||||
<Text style={[ styles.text, styles.rtlText ]}>
|
||||
This is text that will always display RTL.
|
||||
أحب اللغة العربية
|
||||
</Text>
|
||||
<Text style={[ styles.text, styles.textAlign ]}>
|
||||
textAlign toggles
|
||||
</Text>
|
||||
<View style={styles.horizontal}>
|
||||
<View style={[ styles.box, { backgroundColor: 'lightblue' } ]}>
|
||||
<Text>One</Text>
|
||||
</View>
|
||||
<View style={[ styles.box ]}>
|
||||
<Text>Two</Text>
|
||||
</View>
|
||||
</View>
|
||||
<TouchableHighlight
|
||||
onPress={this._handleToggle}
|
||||
style={styles.toggle}
|
||||
@@ -34,8 +42,8 @@ class I18nManagerExample extends Component {
|
||||
}
|
||||
|
||||
_handleToggle = () => {
|
||||
this._isRTL = !this._isRTL
|
||||
I18nManager.setPreferredLanguageRTL(this._isRTL)
|
||||
I18nManager.setPreferredLanguageRTL(!I18nManager.isRTL)
|
||||
this.forceUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,13 +63,16 @@ const styles = StyleSheet.create({
|
||||
fontSize: 18,
|
||||
marginBottom: 5
|
||||
},
|
||||
ltrText: {
|
||||
textAlign$noI18n: 'left',
|
||||
writingDirection$noI18n: 'ltr'
|
||||
textAlign: {
|
||||
textAlign: 'left'
|
||||
},
|
||||
rtlText: {
|
||||
textAlign$noI18n: 'right',
|
||||
writingDirection$noI18n: 'rtl'
|
||||
horizontal: {
|
||||
flexDirection: 'row',
|
||||
marginVertical: 10
|
||||
},
|
||||
box: {
|
||||
borderWidth: 1,
|
||||
flex: 1
|
||||
},
|
||||
toggle: {
|
||||
alignSelf: 'center',
|
||||
|
||||
@@ -271,7 +271,7 @@ const examples = [
|
||||
<Text>
|
||||
auto (default) - english LTR
|
||||
</Text>
|
||||
<Text style={{ writingDirection$noI18n: 'rtl' }}>
|
||||
<Text>
|
||||
أحب اللغة العربية auto (default) - arabic RTL
|
||||
</Text>
|
||||
<Text style={{textAlign: 'left'}}>
|
||||
|
||||
@@ -25,33 +25,6 @@ Object {
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`apis/StyleSheet/i18nStyle LTR mode normalizes properties 1`] = `
|
||||
Object {
|
||||
"borderBottomLeftRadius": 20,
|
||||
"borderBottomRightRadius": "2rem",
|
||||
"borderLeftColor": "red",
|
||||
"borderLeftStyle": "solid",
|
||||
"borderLeftWidth": 5,
|
||||
"borderRightColor": "blue",
|
||||
"borderRightStyle": "dotted",
|
||||
"borderRightWidth": 6,
|
||||
"borderTopLeftRadius": 10,
|
||||
"borderTopRightRadius": "1rem",
|
||||
"left": 1,
|
||||
"marginLeft": 7,
|
||||
"marginRight": 8,
|
||||
"paddingLeft": 9,
|
||||
"paddingRight": 10,
|
||||
"right": 2,
|
||||
"textAlign": "left",
|
||||
"textShadowOffset": Object {
|
||||
"height": 10,
|
||||
"width": "1rem",
|
||||
},
|
||||
"writingDirection": "ltr",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`apis/StyleSheet/i18nStyle RTL mode does auto-flip 1`] = `
|
||||
Object {
|
||||
"borderBottomLeftRadius": "2rem",
|
||||
@@ -78,30 +51,3 @@ Object {
|
||||
"writingDirection": "rtl",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`apis/StyleSheet/i18nStyle RTL mode normalizes properties 1`] = `
|
||||
Object {
|
||||
"borderBottomLeftRadius": 20,
|
||||
"borderBottomRightRadius": "2rem",
|
||||
"borderLeftColor": "red",
|
||||
"borderLeftStyle": "solid",
|
||||
"borderLeftWidth": 5,
|
||||
"borderRightColor": "blue",
|
||||
"borderRightStyle": "dotted",
|
||||
"borderRightWidth": 6,
|
||||
"borderTopLeftRadius": 10,
|
||||
"borderTopRightRadius": "1rem",
|
||||
"left": 1,
|
||||
"marginLeft": 7,
|
||||
"marginRight": 8,
|
||||
"paddingLeft": 9,
|
||||
"paddingRight": 10,
|
||||
"right": 2,
|
||||
"textAlign": "left",
|
||||
"textShadowOffset": Object {
|
||||
"height": 10,
|
||||
"width": "-1rem",
|
||||
},
|
||||
"writingDirection": "ltr",
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -21,16 +21,9 @@ const style = {
|
||||
paddingRight: 10,
|
||||
right: 2,
|
||||
textAlign: 'left',
|
||||
textShadowOffset: { width: '1rem', height: 10 },
|
||||
writingDirection: 'ltr'
|
||||
textShadowOffset: { width: '1rem', height: 10 }
|
||||
};
|
||||
|
||||
const styleNoI18n = Object.keys(style).reduce((acc, prop) => {
|
||||
const newProp = `${prop}$noI18n`;
|
||||
acc[newProp] = style[prop];
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
describe('apis/StyleSheet/i18nStyle', () => {
|
||||
describe('LTR mode', () => {
|
||||
beforeEach(() => {
|
||||
@@ -44,9 +37,6 @@ describe('apis/StyleSheet/i18nStyle', () => {
|
||||
test('does not auto-flip', () => {
|
||||
expect(i18nStyle(style)).toMatchSnapshot();
|
||||
});
|
||||
test('normalizes properties', () => {
|
||||
expect(i18nStyle(styleNoI18n)).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('RTL mode', () => {
|
||||
@@ -61,8 +51,5 @@ describe('apis/StyleSheet/i18nStyle', () => {
|
||||
test('does auto-flip', () => {
|
||||
expect(i18nStyle(style)).toMatchSnapshot();
|
||||
});
|
||||
test('normalizes properties', () => {
|
||||
expect(i18nStyle(styleNoI18n)).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -37,14 +37,11 @@ const alphaSortProps = (propsArray) => propsArray.sort((a, b) => {
|
||||
return 0;
|
||||
});
|
||||
|
||||
const expandStyle = (style) => {
|
||||
if (!style) { return emptyObject; }
|
||||
const styleProps = Object.keys(style);
|
||||
const sortedStyleProps = alphaSortProps(styleProps);
|
||||
const createReducer = (style, styleProps) => {
|
||||
let hasResolvedBoxShadow = false;
|
||||
let hasResolvedTextShadow = false;
|
||||
|
||||
const reducer = (resolvedStyle, prop) => {
|
||||
return (resolvedStyle, prop) => {
|
||||
const value = normalizeValue(prop, style[prop]);
|
||||
if (value == null) { return resolvedStyle; }
|
||||
|
||||
@@ -105,7 +102,13 @@ const expandStyle = (style) => {
|
||||
|
||||
return resolvedStyle;
|
||||
};
|
||||
};
|
||||
|
||||
const expandStyle = (style) => {
|
||||
if (!style) { return emptyObject; }
|
||||
const styleProps = Object.keys(style);
|
||||
const sortedStyleProps = alphaSortProps(styleProps);
|
||||
const reducer = createReducer(style, styleProps);
|
||||
const resolvedStyle = sortedStyleProps.reduce(reducer, {});
|
||||
return resolvedStyle;
|
||||
};
|
||||
|
||||
@@ -31,10 +31,6 @@ const PROPERTIES_SWAP_LEFT_RIGHT = {
|
||||
'textAlign': true
|
||||
};
|
||||
|
||||
const PROPERTIES_SWAP_LTR_RTL = {
|
||||
'writingDirection': true
|
||||
};
|
||||
|
||||
/**
|
||||
* Invert the sign of a numeric-like value
|
||||
*/
|
||||
@@ -43,7 +39,7 @@ const additiveInverse = (value: String | Number) => multiplyStyleLengthValue(val
|
||||
/**
|
||||
* BiDi flip the given property.
|
||||
*/
|
||||
const flipProperty = (prop:String): String => {
|
||||
const flipProperty = (prop: String): String => {
|
||||
return PROPERTIES_TO_SWAP.hasOwnProperty(prop) ? PROPERTIES_TO_SWAP[prop] : prop;
|
||||
};
|
||||
|
||||
@@ -62,49 +58,35 @@ const swapLeftRight = (value:String): String => {
|
||||
return value === 'left' ? 'right' : value === 'right' ? 'left' : value;
|
||||
};
|
||||
|
||||
const swapLtrRtl = (value:String): String => {
|
||||
return value === 'ltr' ? 'rtl' : value === 'rtl' ? 'ltr' : value;
|
||||
};
|
||||
const i18nStyle = (originalStyle) => {
|
||||
if (!I18nManager.isRTL) {
|
||||
return originalStyle;
|
||||
}
|
||||
|
||||
const style = originalStyle || emptyObject;
|
||||
const nextStyle = {};
|
||||
|
||||
const i18nStyle = (style = emptyObject) => {
|
||||
const newStyle = {};
|
||||
for (const prop in style) {
|
||||
if (!Object.prototype.hasOwnProperty.call(style, prop)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const indexOfNoFlip = prop.indexOf('$noI18n');
|
||||
|
||||
if (I18nManager.isRTL) {
|
||||
if (PROPERTIES_TO_SWAP[prop]) {
|
||||
const newProp = flipProperty(prop);
|
||||
newStyle[newProp] = style[prop];
|
||||
} else if (PROPERTIES_SWAP_LEFT_RIGHT[prop]) {
|
||||
newStyle[prop] = swapLeftRight(style[prop]);
|
||||
} else if (PROPERTIES_SWAP_LTR_RTL[prop]) {
|
||||
newStyle[prop] = swapLtrRtl(style[prop]);
|
||||
} else if (prop === 'textShadowOffset') {
|
||||
newStyle[prop] = style[prop];
|
||||
newStyle[prop].width = additiveInverse(style[prop].width);
|
||||
} else if (prop === 'transform') {
|
||||
newStyle[prop] = style[prop].map(flipTransform);
|
||||
} else if (indexOfNoFlip > -1) {
|
||||
const newProp = prop.substring(0, indexOfNoFlip);
|
||||
newStyle[newProp] = style[prop];
|
||||
} else {
|
||||
newStyle[prop] = style[prop];
|
||||
}
|
||||
if (PROPERTIES_TO_SWAP[prop]) {
|
||||
const newProp = flipProperty(prop);
|
||||
nextStyle[newProp] = style[prop];
|
||||
} else if (PROPERTIES_SWAP_LEFT_RIGHT[prop]) {
|
||||
nextStyle[prop] = swapLeftRight(style[prop]);
|
||||
} else if (prop === 'textShadowOffset') {
|
||||
nextStyle[prop] = style[prop];
|
||||
nextStyle[prop].width = additiveInverse(style[prop].width);
|
||||
} else if (prop === 'transform') {
|
||||
nextStyle[prop] = style[prop].map(flipTransform);
|
||||
} else {
|
||||
if (indexOfNoFlip > -1) {
|
||||
const newProp = prop.substring(0, indexOfNoFlip);
|
||||
newStyle[newProp] = style[prop];
|
||||
} else {
|
||||
newStyle[prop] = style[prop];
|
||||
}
|
||||
nextStyle[prop] = style[prop];
|
||||
}
|
||||
}
|
||||
|
||||
return newStyle;
|
||||
return nextStyle;
|
||||
};
|
||||
|
||||
module.exports = i18nStyle;
|
||||
|
||||
@@ -7,15 +7,20 @@ import createReactDOMStyle from './createReactDOMStyle';
|
||||
import flattenArray from '../../modules/flattenArray';
|
||||
import flattenStyle from './flattenStyle';
|
||||
import generateCss from './generateCss';
|
||||
import I18nManager from '../I18nManager';
|
||||
import injector from './injector';
|
||||
import mapKeyValue from '../../modules/mapKeyValue';
|
||||
import prefixInlineStyles from './prefixInlineStyles';
|
||||
import ReactNativePropRegistry from '../../modules/ReactNativePropRegistry';
|
||||
|
||||
const prefix = 'r-';
|
||||
const SPACE_REGEXP = /\s/g;
|
||||
const ESCAPE_SELECTOR_CHARS_REGEXP = /[(),":?.%\\$#*]/g;
|
||||
|
||||
const createCacheKey = (id) => {
|
||||
const prefix = I18nManager.isRTL ? 'rtl' : 'ltr';
|
||||
return `${prefix}-${id}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates an HTML class name for use on elements
|
||||
*/
|
||||
@@ -64,7 +69,7 @@ const registerStyle = (id, flatStyle) => {
|
||||
}
|
||||
});
|
||||
|
||||
const key = `${prefix}${id}`;
|
||||
const key = createCacheKey(id);
|
||||
resolvedPropsCache[key] = { className };
|
||||
|
||||
return id;
|
||||
@@ -166,7 +171,7 @@ const StyleRegistry = {
|
||||
|
||||
// fast and cachable
|
||||
if (typeof reactNativeStyle === 'number') {
|
||||
const key = `${prefix}${reactNativeStyle}`;
|
||||
const key = createCacheKey(reactNativeStyle);
|
||||
return resolvePropsIfNeeded(key, reactNativeStyle);
|
||||
}
|
||||
|
||||
@@ -187,30 +192,8 @@ const StyleRegistry = {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: determine when/if to cache unregistered styles. This produces 2x
|
||||
// faster benchmark results for unregistered styles. However, the cache
|
||||
// could be filled with props that are never used again.
|
||||
//
|
||||
// let hasValidKey = true;
|
||||
// let key = flatArray.reduce((keyParts, element) => {
|
||||
// if (typeof element === 'number') {
|
||||
// keyParts.push(element);
|
||||
// } else {
|
||||
// if (element.transform) {
|
||||
// hasValidKey = false;
|
||||
// } else {
|
||||
// const objectAsKey = Object.keys(element).map((prop) => `${prop}:${element[prop]}`).join(';');
|
||||
// if (objectAsKey !== '') {
|
||||
// keyParts.push(objectAsKey);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// return keyParts;
|
||||
// }, [ prefix ]).join('-');
|
||||
// if (!hasValidKey) { key = null; }
|
||||
|
||||
// cache resolved props when all styles are registered
|
||||
const key = isArrayOfNumbers ? `${prefix}${flatArray.join('-')}` : null;
|
||||
const key = isArrayOfNumbers ? createCacheKey(flatArray.join('-')) : null;
|
||||
|
||||
return resolvePropsIfNeeded(key, flatArray);
|
||||
}
|
||||
|
||||
@@ -32,11 +32,7 @@ const TextOnlyStylePropTypes = {
|
||||
whiteSpace: string,
|
||||
wordWrap: string,
|
||||
MozOsxFontSmoothing: string,
|
||||
WebkitFontSmoothing: string,
|
||||
// opt-out of RTL flipping
|
||||
textAlign$noI18n: TextAlignPropType,
|
||||
textShadowOffset$noI18n: ShadowOffsetPropType,
|
||||
writingDirection$noI18n: WritingDirectionPropType
|
||||
WebkitFontSmoothing: string
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
|
||||
@@ -19,16 +19,7 @@ const BorderPropTypes = {
|
||||
borderTopStyle: BorderStylePropType,
|
||||
borderRightStyle: BorderStylePropType,
|
||||
borderBottomStyle: BorderStylePropType,
|
||||
borderLeftStyle: BorderStylePropType,
|
||||
/* Props to opt-out of RTL flipping */
|
||||
borderLeftColor$noI18n: ColorPropType,
|
||||
borderRightColor$noI18n: ColorPropType,
|
||||
borderTopLeftRadius$noI18n: numberOrString,
|
||||
borderTopRightRadius$noI18n: numberOrString,
|
||||
borderBottomLeftRadius$noI18n: numberOrString,
|
||||
borderBottomRightRadius$noI18n: numberOrString,
|
||||
borderLeftStyle$noI18n: BorderStylePropType,
|
||||
borderRightStyle$noI18n: BorderStylePropType
|
||||
borderLeftStyle: BorderStylePropType
|
||||
};
|
||||
|
||||
module.exports = BorderPropTypes;
|
||||
|
||||
@@ -48,16 +48,7 @@ const LayoutPropTypes = {
|
||||
left: numberOrString,
|
||||
position: oneOf([ 'absolute', 'fixed', 'relative', 'static' ]),
|
||||
right: numberOrString,
|
||||
top: numberOrString,
|
||||
// opt-out of RTL flipping
|
||||
borderLeftWidth$noI18n: numberOrString,
|
||||
borderRightWidth$noI18n: numberOrString,
|
||||
left$noI18n: numberOrString,
|
||||
marginLeft$noI18n: numberOrString,
|
||||
marginRight$noI18n: numberOrString,
|
||||
paddingLeft$noI18n: numberOrString,
|
||||
paddingRight$noI18n: numberOrString,
|
||||
right$noI18n: numberOrString
|
||||
top: numberOrString
|
||||
};
|
||||
|
||||
module.exports = LayoutPropTypes;
|
||||
|
||||
Reference in New Issue
Block a user