mirror of
https://github.com/zoriya/react-native-web.git
synced 2026-06-08 12:32:58 +00:00
[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:
+14
-5
@@ -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();
|
||||||
|
|||||||
+7
-5
@@ -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);
|
||||||
|
|||||||
Vendored
+28
-6
@@ -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();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+25
-3
@@ -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 {
|
||||||
|
|||||||
-105
@@ -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",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
+152
-71
@@ -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
@@ -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>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user