mirror of
https://github.com/zoriya/react-native-web.git
synced 2026-06-08 04:31:19 +00:00
[add] StyleSheet support for start/end properties and values
Add support for new style properties and values that automatically
account for the writing direction (as introduced in React Native
0.51.0). The start/end variants are automatically resolved to match the
global writing direction, as defined by I18nManager.isRTL. Start/End
take precedence over Left/Right.
Adds support for the following properties:
* `borderTop{End,Start}Radius`
* `borderBottom{End,Start}Radius`
* `border{End,Start}Color`
* `border{End,Start}Style`
* `border{End,Start}Width`
* `end`
* `margin{End,Start}`
* `padding{End,Start}`
* `start`
And values:
* `clear: "end" | "start"`
* `float: "end" | "start"`
* `textAlign: "end" | "start"`
This commit is contained in:
@@ -92,7 +92,7 @@ StyleSheetValidation.addValidStylePropTypes({
|
||||
clear: string,
|
||||
cursor: string,
|
||||
fill: string,
|
||||
float: oneOf(['left', 'none', 'right']),
|
||||
float: oneOf(['end', 'left', 'none', 'right', 'start']),
|
||||
listStyle: string,
|
||||
pointerEvents: string,
|
||||
tableLayout: string,
|
||||
|
||||
+54
-2
@@ -1,6 +1,6 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`StyleSheet/i18nStyle LTR mode does not auto-flip 1`] = `
|
||||
exports[`StyleSheet/i18nStyle LTR mode converts and doesn't flip start/end 1`] = `
|
||||
Object {
|
||||
"borderBottomLeftRadius": 20,
|
||||
"borderBottomRightRadius": "2rem",
|
||||
@@ -26,7 +26,59 @@ Object {
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`StyleSheet/i18nStyle RTL mode does auto-flip 1`] = `
|
||||
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,
|
||||
|
||||
+68
-5
@@ -3,7 +3,7 @@
|
||||
import I18nManager from '../../I18nManager';
|
||||
import i18nStyle from '../i18nStyle';
|
||||
|
||||
const style = {
|
||||
const styleLeftRight = {
|
||||
borderLeftColor: 'red',
|
||||
borderRightColor: 'blue',
|
||||
borderTopLeftRadius: 10,
|
||||
@@ -24,6 +24,27 @@ const style = {
|
||||
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', () => {
|
||||
beforeEach(() => {
|
||||
@@ -34,8 +55,29 @@ describe('StyleSheet/i18nStyle', () => {
|
||||
I18nManager.allowRTL(true);
|
||||
});
|
||||
|
||||
test('does not auto-flip', () => {
|
||||
expect(i18nStyle(style)).toMatchSnapshot();
|
||||
test("doesn't flip left/right", () => {
|
||||
expect(i18nStyle(styleLeftRight)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test("converts and doesn't flip start/end", () => {
|
||||
expect(i18nStyle(styleStartEnd)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('start/end takes precedence over left/right', () => {
|
||||
const style = {
|
||||
borderTopStartRadius: 10,
|
||||
borderTopLeftRadius: 0,
|
||||
end: 10,
|
||||
right: 0,
|
||||
marginStart: 10,
|
||||
marginLeft: 0
|
||||
};
|
||||
const expected = {
|
||||
borderTopLeftRadius: 10,
|
||||
marginLeft: 10,
|
||||
right: 10
|
||||
};
|
||||
expect(i18nStyle(style)).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -48,8 +90,29 @@ describe('StyleSheet/i18nStyle', () => {
|
||||
I18nManager.forceRTL(false);
|
||||
});
|
||||
|
||||
test('does auto-flip', () => {
|
||||
expect(i18nStyle(style)).toMatchSnapshot();
|
||||
test('flips left/right', () => {
|
||||
expect(i18nStyle(styleLeftRight)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('converts and flips start/end', () => {
|
||||
expect(i18nStyle(styleStartEnd)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+106
-45
@@ -13,75 +13,136 @@ import multiplyStyleLengthValue from '../../modules/multiplyStyleLengthValue';
|
||||
|
||||
const emptyObject = {};
|
||||
|
||||
/**
|
||||
* Map of property names to their BiDi equivalent.
|
||||
*/
|
||||
const PROPERTIES_TO_SWAP = {
|
||||
borderTopLeftRadius: 'borderTopRightRadius',
|
||||
borderTopRightRadius: 'borderTopLeftRadius',
|
||||
borderBottomLeftRadius: 'borderBottomRightRadius',
|
||||
borderBottomRightRadius: 'borderBottomLeftRadius',
|
||||
borderLeftColor: 'borderRightColor',
|
||||
borderLeftStyle: 'borderRightStyle',
|
||||
borderLeftWidth: 'borderRightWidth',
|
||||
borderRightColor: 'borderLeftColor',
|
||||
borderRightWidth: 'borderLeftWidth',
|
||||
borderRightStyle: 'borderLeftStyle',
|
||||
left: 'right',
|
||||
marginLeft: 'marginRight',
|
||||
marginRight: 'marginLeft',
|
||||
paddingLeft: 'paddingRight',
|
||||
paddingRight: 'paddingLeft',
|
||||
right: 'left'
|
||||
const borderTopLeftRadius = 'borderTopLeftRadius';
|
||||
const borderTopRightRadius = 'borderTopRightRadius';
|
||||
const borderBottomLeftRadius = 'borderBottomLeftRadius';
|
||||
const borderBottomRightRadius = 'borderBottomRightRadius';
|
||||
const borderLeftColor = 'borderLeftColor';
|
||||
const borderLeftStyle = 'borderLeftStyle';
|
||||
const borderLeftWidth = 'borderLeftWidth';
|
||||
const borderRightColor = 'borderRightColor';
|
||||
const borderRightStyle = 'borderRightStyle';
|
||||
const borderRightWidth = 'borderRightWidth';
|
||||
const right = 'right';
|
||||
const marginLeft = 'marginLeft';
|
||||
const marginRight = 'marginRight';
|
||||
const paddingLeft = 'paddingLeft';
|
||||
const paddingRight = 'paddingRight';
|
||||
const left = 'left';
|
||||
|
||||
// Map of LTR property names to their BiDi equivalent.
|
||||
const PROPERTIES_FLIP = {
|
||||
borderTopLeftRadius: borderTopRightRadius,
|
||||
borderTopRightRadius: borderTopLeftRadius,
|
||||
borderBottomLeftRadius: borderBottomRightRadius,
|
||||
borderBottomRightRadius: borderBottomLeftRadius,
|
||||
borderLeftColor: borderRightColor,
|
||||
borderLeftStyle: borderRightStyle,
|
||||
borderLeftWidth: borderRightWidth,
|
||||
borderRightColor: borderLeftColor,
|
||||
borderRightStyle: borderLeftStyle,
|
||||
borderRightWidth: borderLeftWidth,
|
||||
left: right,
|
||||
marginLeft: marginRight,
|
||||
marginRight: marginLeft,
|
||||
paddingLeft: paddingRight,
|
||||
paddingRight: paddingLeft,
|
||||
right: left
|
||||
};
|
||||
|
||||
const PROPERTIES_SWAP_LEFT_RIGHT = {
|
||||
// Map of I18N property names to their LTR equivalent.
|
||||
const PROPERTIES_I18N = {
|
||||
borderTopStartRadius: borderTopLeftRadius,
|
||||
borderTopEndRadius: borderTopRightRadius,
|
||||
borderBottomStartRadius: borderBottomLeftRadius,
|
||||
borderBottomEndRadius: borderBottomRightRadius,
|
||||
borderStartColor: borderLeftColor,
|
||||
borderStartStyle: borderLeftStyle,
|
||||
borderStartWidth: borderLeftWidth,
|
||||
borderEndColor: borderRightColor,
|
||||
borderEndStyle: borderRightStyle,
|
||||
borderEndWidth: borderRightWidth,
|
||||
end: right,
|
||||
marginStart: marginLeft,
|
||||
marginEnd: marginRight,
|
||||
paddingStart: paddingLeft,
|
||||
paddingEnd: paddingRight,
|
||||
start: left
|
||||
};
|
||||
|
||||
const PROPERTIES_VALUE = {
|
||||
clear: true,
|
||||
float: true,
|
||||
textAlign: true
|
||||
};
|
||||
|
||||
/**
|
||||
* Invert the sign of a numeric-like value
|
||||
*/
|
||||
// Invert the sign of a numeric-like value
|
||||
const additiveInverse = (value: String | Number) => multiplyStyleLengthValue(value, -1);
|
||||
|
||||
/**
|
||||
* BiDi flip the given property.
|
||||
*/
|
||||
const flipProperty = (prop: String): String => {
|
||||
return PROPERTIES_TO_SWAP.hasOwnProperty(prop) ? PROPERTIES_TO_SWAP[prop] : prop;
|
||||
// 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;
|
||||
};
|
||||
|
||||
const swapLeftRight = (value: String): String => {
|
||||
// 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 => {
|
||||
if (!I18nManager.isRTL) {
|
||||
return originalStyle;
|
||||
}
|
||||
const isRTL = I18nManager.isRTL;
|
||||
|
||||
const style = originalStyle || emptyObject;
|
||||
const nextStyle = {};
|
||||
const frozenProps = {};
|
||||
|
||||
for (const prop in style) {
|
||||
if (!Object.prototype.hasOwnProperty.call(style, prop)) {
|
||||
for (const originalProp in style) {
|
||||
if (!Object.prototype.hasOwnProperty.call(style, originalProp)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const value = style[prop];
|
||||
let prop = originalProp;
|
||||
let value = style[originalProp];
|
||||
let shouldFreezeProp = false;
|
||||
|
||||
if (PROPERTIES_TO_SWAP[prop]) {
|
||||
const newProp = flipProperty(prop);
|
||||
nextStyle[newProp] = value;
|
||||
} else if (PROPERTIES_SWAP_LEFT_RIGHT[prop]) {
|
||||
nextStyle[prop] = swapLeftRight(value);
|
||||
} else if (prop === 'textShadowOffset') {
|
||||
nextStyle[prop] = value;
|
||||
nextStyle[prop].width = additiveInverse(value.width);
|
||||
// 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);
|
||||
}
|
||||
|
||||
if (isRTL) {
|
||||
if (PROPERTIES_FLIP[prop]) {
|
||||
const newProp = flipProperty(prop);
|
||||
if (!frozenProps[prop]) {
|
||||
nextStyle[newProp] = value;
|
||||
}
|
||||
} 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 {
|
||||
nextStyle[prop] = style[prop];
|
||||
if (!frozenProps[prop]) {
|
||||
nextStyle[prop] = value;
|
||||
}
|
||||
}
|
||||
|
||||
// Mark the style prop as frozen
|
||||
if (shouldFreezeProp) {
|
||||
frozenProps[prop] = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,16 @@ import { number, oneOf, oneOfType, shape, string } from 'prop-types';
|
||||
const numberOrString = oneOfType([number, string]);
|
||||
|
||||
const ShadowOffsetPropType = shape({ width: number, height: number });
|
||||
const TextAlignPropType = oneOf(['center', 'inherit', 'justify', 'justify-all', 'left', 'right']);
|
||||
const TextAlignPropType = oneOf([
|
||||
'center',
|
||||
'end',
|
||||
'inherit',
|
||||
'justify',
|
||||
'justify-all',
|
||||
'left',
|
||||
'right',
|
||||
'start'
|
||||
]);
|
||||
const WritingDirectionPropType = oneOf(['auto', 'ltr', 'rtl']);
|
||||
|
||||
const TextStylePropTypes = {
|
||||
|
||||
@@ -27,13 +27,16 @@ const LayoutPropTypes = {
|
||||
backfaceVisibility: hiddenOrVisible,
|
||||
borderWidth: numberOrString,
|
||||
borderBottomWidth: numberOrString,
|
||||
borderEndWidth: numberOrString,
|
||||
borderLeftWidth: numberOrString,
|
||||
borderRightWidth: numberOrString,
|
||||
borderStartWidth: numberOrString,
|
||||
borderTopWidth: numberOrString,
|
||||
bottom: numberOrString,
|
||||
boxSizing: string,
|
||||
direction: oneOf(['inherit', 'ltr', 'rtl']),
|
||||
display: string,
|
||||
end: numberOrString,
|
||||
flex: number,
|
||||
flexBasis: numberOrString,
|
||||
flexDirection: oneOf(['column', 'column-reverse', 'row', 'row-reverse']),
|
||||
@@ -53,8 +56,10 @@ const LayoutPropTypes = {
|
||||
margin: numberOrString,
|
||||
marginBottom: numberOrString,
|
||||
marginHorizontal: numberOrString,
|
||||
marginEnd: numberOrString,
|
||||
marginLeft: numberOrString,
|
||||
marginRight: numberOrString,
|
||||
marginStart: numberOrString,
|
||||
marginTop: numberOrString,
|
||||
marginVertical: numberOrString,
|
||||
maxHeight: numberOrString,
|
||||
@@ -68,12 +73,15 @@ const LayoutPropTypes = {
|
||||
padding: numberOrString,
|
||||
paddingBottom: numberOrString,
|
||||
paddingHorizontal: numberOrString,
|
||||
paddingEnd: numberOrString,
|
||||
paddingLeft: numberOrString,
|
||||
paddingRight: numberOrString,
|
||||
paddingStart: numberOrString,
|
||||
paddingTop: numberOrString,
|
||||
paddingVertical: numberOrString,
|
||||
position: oneOf(['absolute', 'fixed', 'relative', 'static', 'sticky']),
|
||||
right: numberOrString,
|
||||
start: numberOrString,
|
||||
top: numberOrString,
|
||||
visibility: hiddenOrVisible,
|
||||
width: numberOrString,
|
||||
|
||||
Reference in New Issue
Block a user