[add] I18nManager and StyleSheet support for RTL without left/right flip

I18nManager supports `doLeftAndRightSwapInRTL` and
`swapLeftAndRightInRTL` to query and control the BiDi-flipping of
left/right properties and values. For example, you may choose to use
`end`/`start` for positioning that flips with writing direction, and
then disable `left`/`right` swapping in RTL so that `left` will always
be `left`.

The StyleSheet resolver cache must also account for the third "direction"
variant: RTL with no swapping of left/right.
This commit is contained in:
Nicolas Gallagher
2018-02-14 13:13:21 -08:00
parent b754776373
commit 92794cdc9f
8 changed files with 275 additions and 247 deletions
+14 -5
View File
@@ -14,11 +14,14 @@ import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment';
type I18nManagerStatus = { type I18nManagerStatus = {
allowRTL: (allowRTL: boolean) => void, allowRTL: (allowRTL: boolean) => void,
doLeftAndRightSwapInRTL: boolean,
forceRTL: (forceRTL: boolean) => void, forceRTL: (forceRTL: boolean) => void,
isRTL: boolean,
setPreferredLanguageRTL: (setRTL: boolean) => void, setPreferredLanguageRTL: (setRTL: boolean) => void,
isRTL: boolean swapLeftAndRightInRTL: (flipStyles: boolean) => void
}; };
let doLeftAndRightSwapInRTL = true;
let isPreferredLanguageRTL = false; let isPreferredLanguageRTL = false;
let isRTLAllowed = true; let isRTLAllowed = true;
let isRTLForced = false; let isRTLForced = false;
@@ -30,7 +33,7 @@ const isRTL = () => {
return isRTLAllowed && isPreferredLanguageRTL; return isRTLAllowed && isPreferredLanguageRTL;
}; };
const onChange = () => { const onDirectionChange = () => {
if (ExecutionEnvironment.canUseDOM) { if (ExecutionEnvironment.canUseDOM) {
if (document.documentElement && document.documentElement.setAttribute) { if (document.documentElement && document.documentElement.setAttribute) {
document.documentElement.setAttribute('dir', isRTL() ? 'rtl' : 'ltr'); document.documentElement.setAttribute('dir', isRTL() ? 'rtl' : 'ltr');
@@ -41,15 +44,21 @@ const onChange = () => {
const I18nManager: I18nManagerStatus = { const I18nManager: I18nManagerStatus = {
allowRTL(bool) { allowRTL(bool) {
isRTLAllowed = bool; isRTLAllowed = bool;
onChange(); onDirectionChange();
}, },
forceRTL(bool) { forceRTL(bool) {
isRTLForced = bool; isRTLForced = bool;
onChange(); onDirectionChange();
}, },
setPreferredLanguageRTL(bool) { setPreferredLanguageRTL(bool) {
isPreferredLanguageRTL = bool; isPreferredLanguageRTL = bool;
onChange(); onDirectionChange();
},
swapLeftAndRightInRTL(bool) {
doLeftAndRightSwapInRTL = bool;
},
get doLeftAndRightSwapInRTL() {
return doLeftAndRightSwapInRTL;
}, },
get isRTL() { get isRTL() {
return isRTL(); return isRTL();
@@ -24,8 +24,8 @@ const emptyObject = {};
export default class ReactNativeStyleResolver { export default class ReactNativeStyleResolver {
_init() { _init() {
this.cache = { ltr: {}, rtl: {} }; this.cache = { ltr: {}, rtl: {}, rtlNoSwap: {} };
this.injectedCache = { ltr: {}, rtl: {} }; this.injectedCache = { ltr: {}, rtl: {}, rtlNoSwap: {} };
this.styleSheetManager = new StyleSheetManager(); this.styleSheetManager = new StyleSheetManager();
} }
@@ -43,7 +43,8 @@ export default class ReactNativeStyleResolver {
} }
_injectRegisteredStyle(id) { _injectRegisteredStyle(id) {
const dir = I18nManager.isRTL ? 'rtl' : 'ltr'; const { doLeftAndRightSwapInRTL, isRTL } = I18nManager;
const dir = isRTL ? (doLeftAndRightSwapInRTL ? 'rtl' : 'rtlNoSwap') : 'ltr';
if (!this.injectedCache[dir][id]) { if (!this.injectedCache[dir][id]) {
const style = flattenStyle(id); const style = flattenStyle(id);
const domStyle = createReactDOMStyle(i18nStyle(style)); const domStyle = createReactDOMStyle(i18nStyle(style));
@@ -120,7 +121,7 @@ export default class ReactNativeStyleResolver {
// Create next DOM style props from current and next RN styles // Create next DOM style props from current and next RN styles
const { classList: rdomClassListNext, style: rdomStyleNext } = this.resolve([ const { classList: rdomClassListNext, style: rdomStyleNext } = this.resolve([
I18nManager.isRTL ? i18nStyle(rnStyle) : rnStyle, i18nStyle(rnStyle),
rnStyleNext rnStyleNext
]); ]);
@@ -196,7 +197,8 @@ export default class ReactNativeStyleResolver {
*/ */
_resolveStyleIfNeeded(style, key) { _resolveStyleIfNeeded(style, key) {
if (key) { if (key) {
const dir = I18nManager.isRTL ? 'rtl' : 'ltr'; const { doLeftAndRightSwapInRTL, isRTL } = I18nManager;
const dir = isRTL ? (doLeftAndRightSwapInRTL ? 'rtl' : 'rtlNoSwap') : 'ltr';
if (!this.cache[dir][key]) { if (!this.cache[dir][key]) {
// slow: convert style object to props and cache // slow: convert style object to props and cache
this.cache[dir][key] = this._resolveStyle(style); this.cache[dir][key] = this._resolveStyle(style);
@@ -42,14 +42,22 @@ describe('StyleSheet/ReactNativeStyleResolver', () => {
testResolve(a, b, c); testResolve(a, b, c);
}); });
test('with register before RTL, resolves to className', () => { test('with register before RTL, resolves to correct className', () => {
const a = ReactNativePropRegistry.register({ left: '12.34%' }); const a = ReactNativePropRegistry.register({ left: '12.34%' });
const b = ReactNativePropRegistry.register({ textAlign: 'left' }); const b = ReactNativePropRegistry.register({ textAlign: 'left' });
const c = ReactNativePropRegistry.register({ marginLeft: 10 }); const c = ReactNativePropRegistry.register({ marginLeft: 10 });
I18nManager.forceRTL(true); I18nManager.forceRTL(true);
const resolved = styleResolver.resolve([a, b, c]);
const resolved1 = styleResolver.resolve([a, b, c]);
expect(resolved1).toMatchSnapshot();
I18nManager.swapLeftAndRightInRTL(false);
const resolved2 = styleResolver.resolve([a, b, c]);
expect(resolved2).toMatchSnapshot();
I18nManager.swapLeftAndRightInRTL(true);
I18nManager.forceRTL(false); I18nManager.forceRTL(false);
expect(resolved).toMatchSnapshot();
}); });
test('with register, resolves to mixed', () => { test('with register, resolves to mixed', () => {
@@ -102,7 +110,7 @@ describe('StyleSheet/ReactNativeStyleResolver', () => {
expect(resolved).toMatchSnapshot(); expect(resolved).toMatchSnapshot();
}); });
test('when RTL=true, resolves to flipped inline styles', () => { test('when isRTL=true, resolves to flipped inline styles', () => {
// note: DOM state resolved from { marginLeft: 5, left: 5 } in RTL mode // note: DOM state resolved from { marginLeft: 5, left: 5 } in RTL mode
node.style.cssText = 'margin-right: 5px; right: 5px;'; node.style.cssText = 'margin-right: 5px; right: 5px;';
I18nManager.forceRTL(true); I18nManager.forceRTL(true);
@@ -111,8 +119,8 @@ describe('StyleSheet/ReactNativeStyleResolver', () => {
expect(resolved).toMatchSnapshot(); expect(resolved).toMatchSnapshot();
}); });
test('when RTL=true, resolves to flipped classNames', () => { test('when isRTL=true, resolves to flipped classNames', () => {
// note: DOM state resolved from { marginLeft: 5, left: 5 } in RTL mode // note: DOM state resolved from { marginLeft: 5, left: 5 }
node.style.cssText = 'margin-right: 5px; right: 5px;'; node.style.cssText = 'margin-right: 5px; right: 5px;';
const nextStyle = ReactNativePropRegistry.register({ marginLeft: 10, right: 1 }); const nextStyle = ReactNativePropRegistry.register({ marginLeft: 10, right: 1 });
@@ -121,5 +129,19 @@ describe('StyleSheet/ReactNativeStyleResolver', () => {
I18nManager.forceRTL(false); I18nManager.forceRTL(false);
expect(resolved).toMatchSnapshot(); expect(resolved).toMatchSnapshot();
}); });
test('when isRTL=true & doLeftAndRightSwapInRTL=false, resolves to non-flipped inline styles', () => {
// note: DOM state resolved from { marginRight 5, right: 5, paddingEnd: 5 }
node.style.cssText = 'margin-right: 5px; right: 5px; padding-left: 5px';
I18nManager.forceRTL(true);
I18nManager.swapLeftAndRightInRTL(false);
const resolved = styleResolver.resolveWithNode(
{ marginRight: 10, right: 10, paddingEnd: 10 },
node
);
I18nManager.forceRTL(false);
I18nManager.swapLeftAndRightInRTL(true);
expect(resolved).toMatchSnapshot();
});
}); });
}); });
@@ -9,7 +9,7 @@ Object {
} }
`; `;
exports[`StyleSheet/ReactNativeStyleResolver resolve with register before RTL, resolves to className 1`] = ` exports[`StyleSheet/ReactNativeStyleResolver resolve with register before RTL, resolves to correct className 1`] = `
Object { Object {
"classList": Array [ "classList": Array [
"rn-marginRight-zso239", "rn-marginRight-zso239",
@@ -20,6 +20,17 @@ Object {
} }
`; `;
exports[`StyleSheet/ReactNativeStyleResolver resolve with register before RTL, resolves to correct className 2`] = `
Object {
"classList": Array [
"rn-left-2s0hu9",
"rn-marginLeft-1n0xq6e",
"rn-textAlign-fdjqy7",
],
"className": "rn-left-2s0hu9 rn-marginLeft-1n0xq6e rn-textAlign-fdjqy7",
}
`;
exports[`StyleSheet/ReactNativeStyleResolver resolve with register, resolves to className 1`] = ` exports[`StyleSheet/ReactNativeStyleResolver resolve with register, resolves to className 1`] = `
Object { Object {
"classList": Array [ "classList": Array [
@@ -246,7 +257,18 @@ Object {
} }
`; `;
exports[`StyleSheet/ReactNativeStyleResolver resolveWithNode when RTL=true, resolves to flipped classNames 1`] = ` exports[`StyleSheet/ReactNativeStyleResolver resolveWithNode when isRTL=true & doLeftAndRightSwapInRTL=false, resolves to non-flipped inline styles 1`] = `
Object {
"className": "",
"style": Object {
"marginRight": "10px",
"paddingLeft": "10px",
"right": "10px",
},
}
`;
exports[`StyleSheet/ReactNativeStyleResolver resolveWithNode when isRTL=true, resolves to flipped classNames 1`] = `
Object { Object {
"className": "rn-left-1u10d71 rn-marginRight-zso239", "className": "rn-left-1u10d71 rn-marginRight-zso239",
"style": Object { "style": Object {
@@ -256,7 +278,7 @@ Object {
} }
`; `;
exports[`StyleSheet/ReactNativeStyleResolver resolveWithNode when RTL=true, resolves to flipped inline styles 1`] = ` exports[`StyleSheet/ReactNativeStyleResolver resolveWithNode when isRTL=true, resolves to flipped inline styles 1`] = `
Object { Object {
"className": "", "className": "",
"style": Object { "style": Object {
@@ -1,105 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`StyleSheet/i18nStyle LTR mode converts and doesn't flip start/end 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",
},
}
`;
exports[`StyleSheet/i18nStyle LTR mode doesn't flip left/right 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",
},
}
`;
exports[`StyleSheet/i18nStyle RTL mode converts and flips start/end 1`] = `
Object {
"borderBottomLeftRadius": "2rem",
"borderBottomRightRadius": 20,
"borderLeftColor": "blue",
"borderLeftStyle": "dotted",
"borderLeftWidth": 6,
"borderRightColor": "red",
"borderRightStyle": "solid",
"borderRightWidth": 5,
"borderTopLeftRadius": "1rem",
"borderTopRightRadius": 10,
"left": 2,
"marginLeft": 8,
"marginRight": 7,
"paddingLeft": 10,
"paddingRight": 9,
"right": 1,
"textAlign": "right",
"textShadowOffset": Object {
"height": 10,
"width": "-1rem",
},
}
`;
exports[`StyleSheet/i18nStyle RTL mode flips left/right 1`] = `
Object {
"borderBottomLeftRadius": "2rem",
"borderBottomRightRadius": 20,
"borderLeftColor": "blue",
"borderLeftStyle": "dotted",
"borderLeftWidth": 6,
"borderRightColor": "red",
"borderRightStyle": "solid",
"borderRightWidth": 5,
"borderTopLeftRadius": "1rem",
"borderTopRightRadius": 10,
"left": 2,
"marginLeft": 8,
"marginRight": 7,
"paddingLeft": 10,
"paddingRight": 9,
"right": 1,
"textAlign": "right",
"textShadowOffset": Object {
"height": 10,
"width": "-1rem",
},
}
`;
@@ -3,50 +3,8 @@
import I18nManager from '../../I18nManager'; import I18nManager from '../../I18nManager';
import i18nStyle from '../i18nStyle'; import i18nStyle from '../i18nStyle';
const styleLeftRight = {
borderLeftColor: 'red',
borderRightColor: 'blue',
borderTopLeftRadius: 10,
borderTopRightRadius: '1rem',
borderBottomLeftRadius: 20,
borderBottomRightRadius: '2rem',
borderLeftStyle: 'solid',
borderRightStyle: 'dotted',
borderLeftWidth: 5,
borderRightWidth: 6,
left: 1,
marginLeft: 7,
marginRight: 8,
paddingLeft: 9,
paddingRight: 10,
right: 2,
textAlign: 'left',
textShadowOffset: { width: '1rem', height: 10 }
};
const styleStartEnd = {
borderStartColor: 'red',
borderEndColor: 'blue',
borderTopStartRadius: 10,
borderTopEndRadius: '1rem',
borderBottomStartRadius: 20,
borderBottomEndRadius: '2rem',
borderStartStyle: 'solid',
borderEndStyle: 'dotted',
borderStartWidth: 5,
borderEndWidth: 6,
start: 1,
marginStart: 7,
marginEnd: 8,
paddingStart: 9,
paddingEnd: 10,
end: 2,
textAlign: 'start',
textShadowOffset: { width: '1rem', height: 10 }
};
describe('StyleSheet/i18nStyle', () => { describe('StyleSheet/i18nStyle', () => {
describe('LTR mode', () => { describe('isRTL = false', () => {
beforeEach(() => { beforeEach(() => {
I18nManager.allowRTL(false); I18nManager.allowRTL(false);
}); });
@@ -56,32 +14,59 @@ describe('StyleSheet/i18nStyle', () => {
}); });
test("doesn't flip left/right", () => { test("doesn't flip left/right", () => {
expect(i18nStyle(styleLeftRight)).toMatchSnapshot(); const initial = {
borderLeftColor: 'red',
left: 1,
marginLeft: 5,
paddingRight: 10,
textAlign: 'right',
textShadowOffset: { width: '1rem', height: 10 }
};
expect(i18nStyle(initial)).toEqual(initial);
}); });
test("converts and doesn't flip start/end", () => { test("converts and doesn't flip start/end", () => {
expect(i18nStyle(styleStartEnd)).toMatchSnapshot(); const initial = {
borderStartColor: 'red',
start: 1,
marginStart: 5,
paddingEnd: 10,
textAlign: 'end',
textShadowOffset: { width: '1rem', height: 10 }
};
const expected = {
borderLeftColor: 'red',
left: 1,
marginLeft: 5,
paddingRight: 10,
textAlign: 'right',
textShadowOffset: { width: '1rem', height: 10 }
};
expect(i18nStyle(initial)).toEqual(expected);
}); });
test('start/end takes precedence over left/right', () => { test('start/end takes precedence over left/right', () => {
const style = { const initial = {
borderTopStartRadius: 10, borderStartWidth: 10,
borderTopLeftRadius: 0, borderLeftWidth: 0,
end: 10, end: 10,
right: 0, right: 0,
marginStart: 10, marginStart: 10,
marginLeft: 0 marginLeft: 0
}; };
const expected = { const expected = {
borderTopLeftRadius: 10, borderLeftWidth: 10,
marginLeft: 10, marginLeft: 10,
right: 10 right: 10
}; };
expect(i18nStyle(style)).toEqual(expected); expect(i18nStyle(initial)).toEqual(expected);
}); });
}); });
describe('RTL mode', () => { describe('isRTL = true', () => {
beforeEach(() => { beforeEach(() => {
I18nManager.forceRTL(true); I18nManager.forceRTL(true);
}); });
@@ -90,29 +75,125 @@ describe('StyleSheet/i18nStyle', () => {
I18nManager.forceRTL(false); I18nManager.forceRTL(false);
}); });
test('flips left/right', () => { describe('doLeftAndRightSwapInRTL = true', () => {
expect(i18nStyle(styleLeftRight)).toMatchSnapshot(); test('flips left/right', () => {
const initial = {
borderLeftColor: 'red',
left: 1,
marginLeft: 5,
paddingRight: 10,
textAlign: 'right',
textShadowOffset: { width: '1rem', height: 10 }
};
const expected = {
borderRightColor: 'red',
right: 1,
marginRight: 5,
paddingLeft: 10,
textAlign: 'left',
textShadowOffset: { width: '-1rem', height: 10 }
};
expect(i18nStyle(initial)).toEqual(expected);
});
test('converts and flips start/end', () => {
const initial = {
borderStartColor: 'red',
start: 1,
marginStart: 5,
paddingEnd: 10,
textAlign: 'end'
};
const expected = {
borderRightColor: 'red',
right: 1,
marginRight: 5,
paddingLeft: 10,
textAlign: 'left'
};
expect(i18nStyle(initial)).toEqual(expected);
});
test('start/end takes precedence over left/right', () => {
const style = {
borderStartWidth: 10,
borderLeftWidth: 0,
end: 10,
right: 0,
marginStart: 10,
marginLeft: 0
};
const expected = {
borderRightWidth: 10,
marginRight: 10,
left: 10
};
expect(i18nStyle(style)).toEqual(expected);
});
}); });
test('converts and flips start/end', () => { describe('doLeftAndRightSwapInRTL = false', () => {
expect(i18nStyle(styleStartEnd)).toMatchSnapshot(); beforeEach(() => {
}); I18nManager.swapLeftAndRightInRTL(false);
});
test('start/end takes precedence over left/right', () => { afterEach(() => {
const style = { I18nManager.swapLeftAndRightInRTL(true);
borderTopStartRadius: 10, });
borderTopLeftRadius: 0,
end: 10, test("doesn't flip left/right", () => {
right: 0, const initial = {
marginStart: 10, borderLeftColor: 'red',
marginLeft: 0 left: 1,
}; marginLeft: 5,
const expected = { paddingRight: 10,
borderTopRightRadius: 10, textAlign: 'right',
marginRight: 10, textShadowOffset: { width: '1rem', height: 10 }
left: 10 };
};
expect(i18nStyle(style)).toEqual(expected); expect(i18nStyle(initial)).toEqual(initial);
});
test('converts start/end', () => {
const initial = {
borderStartColor: 'red',
start: 1,
marginStart: 5,
paddingEnd: 10,
textAlign: 'end'
};
const expected = {
borderRightColor: 'red',
right: 1,
marginRight: 5,
paddingLeft: 10,
textAlign: 'left'
};
expect(i18nStyle(initial)).toEqual(expected);
});
test('start/end takes precedence over left/right', () => {
const style = {
borderStartWidth: 10,
borderRightWidth: 0,
end: 10,
left: 0,
marginStart: 10,
marginRight: 0
};
const expected = {
borderRightWidth: 10,
marginRight: 10,
left: 10
};
expect(i18nStyle(style)).toEqual(expected);
});
}); });
}); });
}); });
+30 -47
View File
@@ -79,69 +79,52 @@ const PROPERTIES_VALUE = {
// Invert the sign of a numeric-like value // Invert the sign of a numeric-like value
const additiveInverse = (value: String | Number) => multiplyStyleLengthValue(value, -1); const additiveInverse = (value: String | Number) => multiplyStyleLengthValue(value, -1);
// Convert I18N properties and values
const convertProperty = (prop: String): String => {
return PROPERTIES_I18N.hasOwnProperty(prop) ? PROPERTIES_I18N[prop] : prop;
};
const convertValue = (value: String): String => {
return value === 'start' ? 'left' : value === 'end' ? 'right' : value;
};
// BiDi flip properties and values
const flipProperty = (prop: String): String => {
return PROPERTIES_FLIP.hasOwnProperty(prop) ? PROPERTIES_FLIP[prop] : prop;
};
const flipValue = (value: String): String => {
return value === 'left' ? 'right' : value === 'right' ? 'left' : value;
};
const i18nStyle = originalStyle => { const i18nStyle = originalStyle => {
const isRTL = I18nManager.isRTL; const { doLeftAndRightSwapInRTL, isRTL } = I18nManager;
const style = originalStyle || emptyObject; const style = originalStyle || emptyObject;
const nextStyle = {};
const frozenProps = {}; const frozenProps = {};
const nextStyle = {};
for (const originalProp in style) { for (const originalProp in style) {
if (!Object.prototype.hasOwnProperty.call(style, originalProp)) { if (!Object.prototype.hasOwnProperty.call(style, originalProp)) {
continue; continue;
} }
const originalValue = style[originalProp];
let prop = originalProp; let prop = originalProp;
let value = style[originalProp]; let value = originalValue;
let shouldFreezeProp = false;
// Process I18N properties and values // BiDi flip properties
if (PROPERTIES_I18N[prop]) { if (PROPERTIES_I18N.hasOwnProperty(originalProp)) {
prop = convertProperty(prop); // convert start/end
// I18N properties takes precendence over left/right const convertedProp = PROPERTIES_I18N[originalProp];
shouldFreezeProp = true; prop = isRTL ? PROPERTIES_FLIP[convertedProp] : convertedProp;
} else if (PROPERTIES_VALUE[prop]) { } else if (isRTL && doLeftAndRightSwapInRTL && PROPERTIES_FLIP[originalProp]) {
value = convertValue(value); prop = PROPERTIES_FLIP[originalProp];
} }
if (isRTL) { // BiDi flip values
if (PROPERTIES_FLIP[prop]) { if (PROPERTIES_VALUE.hasOwnProperty(originalProp)) {
const newProp = flipProperty(prop); if (originalValue === 'start') {
if (!frozenProps[prop]) { value = isRTL ? 'right' : 'left';
nextStyle[newProp] = value; } else if (originalValue === 'end') {
value = isRTL ? 'left' : 'right';
} else if (isRTL && doLeftAndRightSwapInRTL) {
if (originalValue === 'left') {
value = 'right';
} else if (originalValue === 'right') {
value = 'left';
} }
} else if (PROPERTIES_VALUE[prop]) {
nextStyle[prop] = flipValue(value);
} else if (prop === 'textShadowOffset') {
nextStyle[prop] = value;
nextStyle[prop].width = additiveInverse(value.width);
} else {
nextStyle[prop] = style[prop];
}
} else {
if (!frozenProps[prop]) {
nextStyle[prop] = value;
} }
} }
// Mark the style prop as frozen if (isRTL && prop === 'textShadowOffset') {
if (shouldFreezeProp) { nextStyle[prop] = value;
nextStyle[prop].width = additiveInverse(value.width);
} else if (!frozenProps[prop]) {
nextStyle[prop] = value;
}
if (PROPERTIES_I18N[originalProp]) {
frozenProps[prop] = true; frozenProps[prop] = true;
} }
} }
@@ -10,33 +10,47 @@ import UIExplorer, { Description, DocItem, Section, storiesOf } from '../../ui-e
const I18nManagerScreen = () => ( const I18nManagerScreen = () => (
<UIExplorer title="I18nManager" url="2-apis/I18nManager"> <UIExplorer title="I18nManager" url="2-apis/I18nManager">
<Description>Control and set the layout and writing direction of the application.</Description> <Description>
Control and query the layout and writing direction of the application.
</Description>
<Section title="Properties"> <Section title="Properties">
<DocItem <DocItem
name="isRTL" name="isRTL"
typeInfo="boolean = false" typeInfo="boolean = false"
description="Whether the application is currently in RTL mode." description="Whether the application is currently in RTL mode."
/> />
<DocItem
name="doLeftAndRightSwapInRTL"
typeInfo="boolean = true"
description="Whether the application swaps left/right styles in RTL mode."
/>
</Section> </Section>
<Section title="Methods"> <Section title="Methods">
<DocItem <DocItem
name="static allowRTL" name="allowRTL"
typeInfo="(allowRTL: boolean) => void" typeInfo="(allowRTL: boolean) => void"
description="Allow the application to display in RTL mode." description="Allow the application to display in RTL mode."
/> />
<DocItem <DocItem
name="static forceRTL" name="forceRTL"
typeInfo="(forceRTL: boolean) => void" typeInfo="(forceRTL: boolean) => void"
description="Force the application to display in RTL mode." description="Force the application to display in RTL mode."
/> />
<DocItem
name="swapLeftAndRightInRTL"
typeInfo="(flipStyles: boolean) => void"
description="Control whether the application swaps left/right styles in RTL mode. Applications relying on start/end styles may prefer to disable automatic BiDi-flipping of left/right styles."
/>
<DocItem <DocItem
label="web" label="web"
name="static setPreferredLanguageRTL" name="setPreferredLanguageRTL"
typeInfo="(isRTL: boolean) => void" typeInfo="(isRTL: boolean) => void"
description="Set the application's preferred writing direction to RTL. You will need to determine the user's preferred locale server-side (from HTTP headers) and decide whether it's an RTL language." description="Set the application's preferred writing direction to RTL. You may need to infer the user's preferred locale on the server (from HTTP headers) and decide whether it's an RTL language."
/> />
</Section> </Section>