mirror of
https://github.com/zoriya/react-native-web.git
synced 2026-06-07 20:21:25 +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 = {
|
||||
allowRTL: (allowRTL: boolean) => void,
|
||||
doLeftAndRightSwapInRTL: boolean,
|
||||
forceRTL: (forceRTL: boolean) => void,
|
||||
isRTL: boolean,
|
||||
setPreferredLanguageRTL: (setRTL: boolean) => void,
|
||||
isRTL: boolean
|
||||
swapLeftAndRightInRTL: (flipStyles: boolean) => void
|
||||
};
|
||||
|
||||
let doLeftAndRightSwapInRTL = true;
|
||||
let isPreferredLanguageRTL = false;
|
||||
let isRTLAllowed = true;
|
||||
let isRTLForced = false;
|
||||
@@ -30,7 +33,7 @@ const isRTL = () => {
|
||||
return isRTLAllowed && isPreferredLanguageRTL;
|
||||
};
|
||||
|
||||
const onChange = () => {
|
||||
const onDirectionChange = () => {
|
||||
if (ExecutionEnvironment.canUseDOM) {
|
||||
if (document.documentElement && document.documentElement.setAttribute) {
|
||||
document.documentElement.setAttribute('dir', isRTL() ? 'rtl' : 'ltr');
|
||||
@@ -41,15 +44,21 @@ const onChange = () => {
|
||||
const I18nManager: I18nManagerStatus = {
|
||||
allowRTL(bool) {
|
||||
isRTLAllowed = bool;
|
||||
onChange();
|
||||
onDirectionChange();
|
||||
},
|
||||
forceRTL(bool) {
|
||||
isRTLForced = bool;
|
||||
onChange();
|
||||
onDirectionChange();
|
||||
},
|
||||
setPreferredLanguageRTL(bool) {
|
||||
isPreferredLanguageRTL = bool;
|
||||
onChange();
|
||||
onDirectionChange();
|
||||
},
|
||||
swapLeftAndRightInRTL(bool) {
|
||||
doLeftAndRightSwapInRTL = bool;
|
||||
},
|
||||
get doLeftAndRightSwapInRTL() {
|
||||
return doLeftAndRightSwapInRTL;
|
||||
},
|
||||
get isRTL() {
|
||||
return isRTL();
|
||||
|
||||
+7
-5
@@ -24,8 +24,8 @@ const emptyObject = {};
|
||||
|
||||
export default class ReactNativeStyleResolver {
|
||||
_init() {
|
||||
this.cache = { ltr: {}, rtl: {} };
|
||||
this.injectedCache = { ltr: {}, rtl: {} };
|
||||
this.cache = { ltr: {}, rtl: {}, rtlNoSwap: {} };
|
||||
this.injectedCache = { ltr: {}, rtl: {}, rtlNoSwap: {} };
|
||||
this.styleSheetManager = new StyleSheetManager();
|
||||
}
|
||||
|
||||
@@ -43,7 +43,8 @@ export default class ReactNativeStyleResolver {
|
||||
}
|
||||
|
||||
_injectRegisteredStyle(id) {
|
||||
const dir = I18nManager.isRTL ? 'rtl' : 'ltr';
|
||||
const { doLeftAndRightSwapInRTL, isRTL } = I18nManager;
|
||||
const dir = isRTL ? (doLeftAndRightSwapInRTL ? 'rtl' : 'rtlNoSwap') : 'ltr';
|
||||
if (!this.injectedCache[dir][id]) {
|
||||
const style = flattenStyle(id);
|
||||
const domStyle = createReactDOMStyle(i18nStyle(style));
|
||||
@@ -120,7 +121,7 @@ export default class ReactNativeStyleResolver {
|
||||
|
||||
// Create next DOM style props from current and next RN styles
|
||||
const { classList: rdomClassListNext, style: rdomStyleNext } = this.resolve([
|
||||
I18nManager.isRTL ? i18nStyle(rnStyle) : rnStyle,
|
||||
i18nStyle(rnStyle),
|
||||
rnStyleNext
|
||||
]);
|
||||
|
||||
@@ -196,7 +197,8 @@ export default class ReactNativeStyleResolver {
|
||||
*/
|
||||
_resolveStyleIfNeeded(style, 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]) {
|
||||
// slow: convert style object to props and cache
|
||||
this.cache[dir][key] = this._resolveStyle(style);
|
||||
|
||||
Vendored
+28
-6
@@ -42,14 +42,22 @@ describe('StyleSheet/ReactNativeStyleResolver', () => {
|
||||
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 b = ReactNativePropRegistry.register({ textAlign: 'left' });
|
||||
const c = ReactNativePropRegistry.register({ marginLeft: 10 });
|
||||
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);
|
||||
expect(resolved).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('with register, resolves to mixed', () => {
|
||||
@@ -102,7 +110,7 @@ describe('StyleSheet/ReactNativeStyleResolver', () => {
|
||||
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
|
||||
node.style.cssText = 'margin-right: 5px; right: 5px;';
|
||||
I18nManager.forceRTL(true);
|
||||
@@ -111,8 +119,8 @@ describe('StyleSheet/ReactNativeStyleResolver', () => {
|
||||
expect(resolved).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('when RTL=true, resolves to flipped classNames', () => {
|
||||
// note: DOM state resolved from { marginLeft: 5, left: 5 } in RTL mode
|
||||
test('when isRTL=true, resolves to flipped classNames', () => {
|
||||
// note: DOM state resolved from { marginLeft: 5, left: 5 }
|
||||
node.style.cssText = 'margin-right: 5px; right: 5px;';
|
||||
const nextStyle = ReactNativePropRegistry.register({ marginLeft: 10, right: 1 });
|
||||
|
||||
@@ -121,5 +129,19 @@ describe('StyleSheet/ReactNativeStyleResolver', () => {
|
||||
I18nManager.forceRTL(false);
|
||||
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 {
|
||||
"classList": Array [
|
||||
"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`] = `
|
||||
Object {
|
||||
"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 {
|
||||
"className": "rn-left-1u10d71 rn-marginRight-zso239",
|
||||
"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 {
|
||||
"className": "",
|
||||
"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 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('LTR mode', () => {
|
||||
describe('isRTL = false', () => {
|
||||
beforeEach(() => {
|
||||
I18nManager.allowRTL(false);
|
||||
});
|
||||
@@ -56,32 +14,59 @@ describe('StyleSheet/i18nStyle', () => {
|
||||
});
|
||||
|
||||
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", () => {
|
||||
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', () => {
|
||||
const style = {
|
||||
borderTopStartRadius: 10,
|
||||
borderTopLeftRadius: 0,
|
||||
const initial = {
|
||||
borderStartWidth: 10,
|
||||
borderLeftWidth: 0,
|
||||
end: 10,
|
||||
right: 0,
|
||||
marginStart: 10,
|
||||
marginLeft: 0
|
||||
};
|
||||
const expected = {
|
||||
borderTopLeftRadius: 10,
|
||||
borderLeftWidth: 10,
|
||||
marginLeft: 10,
|
||||
right: 10
|
||||
};
|
||||
expect(i18nStyle(style)).toEqual(expected);
|
||||
expect(i18nStyle(initial)).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('RTL mode', () => {
|
||||
describe('isRTL = true', () => {
|
||||
beforeEach(() => {
|
||||
I18nManager.forceRTL(true);
|
||||
});
|
||||
@@ -90,29 +75,125 @@ describe('StyleSheet/i18nStyle', () => {
|
||||
I18nManager.forceRTL(false);
|
||||
});
|
||||
|
||||
test('flips left/right', () => {
|
||||
expect(i18nStyle(styleLeftRight)).toMatchSnapshot();
|
||||
describe('doLeftAndRightSwapInRTL = true', () => {
|
||||
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', () => {
|
||||
expect(i18nStyle(styleStartEnd)).toMatchSnapshot();
|
||||
});
|
||||
describe('doLeftAndRightSwapInRTL = false', () => {
|
||||
beforeEach(() => {
|
||||
I18nManager.swapLeftAndRightInRTL(false);
|
||||
});
|
||||
|
||||
test('start/end takes precedence over left/right', () => {
|
||||
const style = {
|
||||
borderTopStartRadius: 10,
|
||||
borderTopLeftRadius: 0,
|
||||
end: 10,
|
||||
right: 0,
|
||||
marginStart: 10,
|
||||
marginLeft: 0
|
||||
};
|
||||
const expected = {
|
||||
borderTopRightRadius: 10,
|
||||
marginRight: 10,
|
||||
left: 10
|
||||
};
|
||||
expect(i18nStyle(style)).toEqual(expected);
|
||||
afterEach(() => {
|
||||
I18nManager.swapLeftAndRightInRTL(true);
|
||||
});
|
||||
|
||||
test("doesn't flip left/right", () => {
|
||||
const initial = {
|
||||
borderLeftColor: 'red',
|
||||
left: 1,
|
||||
marginLeft: 5,
|
||||
paddingRight: 10,
|
||||
textAlign: 'right',
|
||||
textShadowOffset: { width: '1rem', height: 10 }
|
||||
};
|
||||
|
||||
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
|
||||
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 isRTL = I18nManager.isRTL;
|
||||
|
||||
const { doLeftAndRightSwapInRTL, isRTL } = I18nManager;
|
||||
const style = originalStyle || emptyObject;
|
||||
const nextStyle = {};
|
||||
const frozenProps = {};
|
||||
const nextStyle = {};
|
||||
|
||||
for (const originalProp in style) {
|
||||
if (!Object.prototype.hasOwnProperty.call(style, originalProp)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const originalValue = style[originalProp];
|
||||
let prop = originalProp;
|
||||
let value = style[originalProp];
|
||||
let shouldFreezeProp = false;
|
||||
let value = originalValue;
|
||||
|
||||
// Process I18N properties and values
|
||||
if (PROPERTIES_I18N[prop]) {
|
||||
prop = convertProperty(prop);
|
||||
// I18N properties takes precendence over left/right
|
||||
shouldFreezeProp = true;
|
||||
} else if (PROPERTIES_VALUE[prop]) {
|
||||
value = convertValue(value);
|
||||
// BiDi flip properties
|
||||
if (PROPERTIES_I18N.hasOwnProperty(originalProp)) {
|
||||
// convert start/end
|
||||
const convertedProp = PROPERTIES_I18N[originalProp];
|
||||
prop = isRTL ? PROPERTIES_FLIP[convertedProp] : convertedProp;
|
||||
} else if (isRTL && doLeftAndRightSwapInRTL && PROPERTIES_FLIP[originalProp]) {
|
||||
prop = PROPERTIES_FLIP[originalProp];
|
||||
}
|
||||
|
||||
if (isRTL) {
|
||||
if (PROPERTIES_FLIP[prop]) {
|
||||
const newProp = flipProperty(prop);
|
||||
if (!frozenProps[prop]) {
|
||||
nextStyle[newProp] = value;
|
||||
// BiDi flip values
|
||||
if (PROPERTIES_VALUE.hasOwnProperty(originalProp)) {
|
||||
if (originalValue === 'start') {
|
||||
value = isRTL ? 'right' : 'left';
|
||||
} 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 (shouldFreezeProp) {
|
||||
if (isRTL && prop === 'textShadowOffset') {
|
||||
nextStyle[prop] = value;
|
||||
nextStyle[prop].width = additiveInverse(value.width);
|
||||
} else if (!frozenProps[prop]) {
|
||||
nextStyle[prop] = value;
|
||||
}
|
||||
|
||||
if (PROPERTIES_I18N[originalProp]) {
|
||||
frozenProps[prop] = true;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user