Skip to content

Commit

Permalink
[add] I18nManager and StyleSheet support for RTL without left/right flip
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
necolas committed Feb 16, 2018
1 parent b754776 commit 92794cd
Show file tree
Hide file tree
Showing 8 changed files with 276 additions and 248 deletions.
19 changes: 14 additions & 5 deletions packages/react-native-web/src/exports/I18nManager/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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');
Expand All @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

Expand All @@ -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));
Expand Down Expand Up @@ -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
]);

Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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);
Expand All @@ -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 });

Expand All @@ -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();
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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 [
Expand Down Expand Up @@ -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 {
Expand All @@ -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 {
Expand Down

This file was deleted.

0 comments on commit 92794cd

Please sign in to comment.