diff --git a/docs/components/Text.md b/docs/components/Text.md index 06cd3631..b8f65e2a 100644 --- a/docs/components/Text.md +++ b/docs/components/Text.md @@ -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 diff --git a/docs/components/View.md b/docs/components/View.md index 94ce9443..28c6a896 100644 --- a/docs/components/View.md +++ b/docs/components/View.md @@ -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 diff --git a/docs/guides/internationalization.md b/docs/guides/internationalization.md index b7b511c7..2a7eee13 100644 --- a/docs/guides/internationalization.md +++ b/docs/guides/internationalization.md @@ -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. diff --git a/examples/apis/I18nManager/I18nManagerExample.js b/examples/apis/I18nManager/I18nManagerExample.js index b14348a7..c0cdc5a7 100644 --- a/examples/apis/I18nManager/I18nManagerExample.js +++ b/examples/apis/I18nManager/I18nManagerExample.js @@ -14,14 +14,22 @@ class I18nManagerExample extends Component { LTR/RTL layout example! - This is sample text. The writing direction can be changed by pressing the button below. - - - 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. - This is text that will always display RTL. + أحب اللغة العربية + + textAlign toggles + + + + One + + + Two + + { - 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', diff --git a/examples/components/Text/TextExample.js b/examples/components/Text/TextExample.js index 9259cfa8..b43826b9 100644 --- a/examples/components/Text/TextExample.js +++ b/examples/components/Text/TextExample.js @@ -271,7 +271,7 @@ const examples = [ auto (default) - english LTR - + أحب اللغة العربية auto (default) - arabic RTL diff --git a/src/apis/StyleSheet/__tests__/__snapshots__/i18nStyle-test.js.snap b/src/apis/StyleSheet/__tests__/__snapshots__/i18nStyle-test.js.snap index 52be129b..2f1ea7e6 100644 --- a/src/apis/StyleSheet/__tests__/__snapshots__/i18nStyle-test.js.snap +++ b/src/apis/StyleSheet/__tests__/__snapshots__/i18nStyle-test.js.snap @@ -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", -} -`; diff --git a/src/apis/StyleSheet/__tests__/i18nStyle-test.js b/src/apis/StyleSheet/__tests__/i18nStyle-test.js index 5350f84d..81a53a4e 100644 --- a/src/apis/StyleSheet/__tests__/i18nStyle-test.js +++ b/src/apis/StyleSheet/__tests__/i18nStyle-test.js @@ -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(); - }); }); }); diff --git a/src/apis/StyleSheet/expandStyle.js b/src/apis/StyleSheet/expandStyle.js index 9e628ccb..5ffa8ba3 100644 --- a/src/apis/StyleSheet/expandStyle.js +++ b/src/apis/StyleSheet/expandStyle.js @@ -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; }; diff --git a/src/apis/StyleSheet/i18nStyle.js b/src/apis/StyleSheet/i18nStyle.js index af57184d..dc8cb679 100644 --- a/src/apis/StyleSheet/i18nStyle.js +++ b/src/apis/StyleSheet/i18nStyle.js @@ -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; diff --git a/src/apis/StyleSheet/registry.js b/src/apis/StyleSheet/registry.js index 9682a064..193a6930 100644 --- a/src/apis/StyleSheet/registry.js +++ b/src/apis/StyleSheet/registry.js @@ -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); } diff --git a/src/components/Text/TextStylePropTypes.js b/src/components/Text/TextStylePropTypes.js index 02fa2883..54c4150c 100644 --- a/src/components/Text/TextStylePropTypes.js +++ b/src/components/Text/TextStylePropTypes.js @@ -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 = { diff --git a/src/propTypes/BorderPropTypes.js b/src/propTypes/BorderPropTypes.js index a63d87bc..997dff9e 100644 --- a/src/propTypes/BorderPropTypes.js +++ b/src/propTypes/BorderPropTypes.js @@ -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; diff --git a/src/propTypes/LayoutPropTypes.js b/src/propTypes/LayoutPropTypes.js index 5739f21d..e5c72701 100644 --- a/src/propTypes/LayoutPropTypes.js +++ b/src/propTypes/LayoutPropTypes.js @@ -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;