mirror of
https://github.com/zhigang1992/react-native-web.git
synced 2026-01-12 22:51:09 +08:00
[change] Introduce static CSS base rules for core primitives
This patch addresses 2 related issues: 1) Browser layout times in Chrome increase when elements use a lot of CSS class names. This begins to add up for larger trees. 2) React Native supports passing 'null' values for styles. This can remove base styles defined using 'StyleSheet' in the implementation of components like View and Text. Both of these issues can be avoided, and some runtime logic and computation removed, by moving the base styles to static CSS rules. Comparisons of the "benchmark" UI tests (which only render View) indicate that total times in Chrome are reduced by ~20% with almost all of those savings coming from a ~33% reduction in layout-related timings. To avoid style conflicts, static CSS rules are inserted before atomic CSS rules. The modality-related focus style is now inserted into the in-memory style sheet, making it available in SSR output. Ref #1136 Close #1165
This commit is contained in:
@@ -10,40 +10,17 @@ exports[`AppRegistry getApplication "getStyleElement" produces styles that are a
|
||||
`;
|
||||
|
||||
exports[`AppRegistry getApplication "getStyleElement" produces styles that are a function of rendering "element": CSS for an unstyled app 1`] = `
|
||||
"@media all{
|
||||
":focus:not([data-rn-focusvisible-x92cna]){outline: none;}
|
||||
@media all{
|
||||
html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(0,0,0,0);}
|
||||
body{margin:0;}
|
||||
button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0;}
|
||||
input::-webkit-inner-spin-button,input::-webkit-outer-spin-button,input::-webkit-search-cancel-button,input::-webkit-search-decoration,input::-webkit-search-results-button,input::-webkit-search-results-decoration{display:none;}
|
||||
.rn-reset{background-color:transparent;color:inherit;font:inherit;list-style:none;margin:0;text-align:inherit;text-decoration:none;}
|
||||
.rn-pointer{cursor:pointer;}
|
||||
}
|
||||
.rn-pointerEvents-12vffkv > *{pointer-events:auto}
|
||||
.rn-pointerEvents-12vffkv{pointer-events:none !important}
|
||||
.rn-alignItems-1oszu61{-ms-flex-align:stretch;-webkit-align-items:stretch;-webkit-box-align:stretch;align-items:stretch}
|
||||
.rn-borderTopStyle-1efd50x{border-top-style:solid}
|
||||
.rn-borderRightStyle-14skgim{border-right-style:solid}
|
||||
.rn-borderBottomStyle-rull8r{border-bottom-style:solid}
|
||||
.rn-borderLeftStyle-mm0ijv{border-left-style:solid}
|
||||
.rn-borderTopWidth-13yce4e{border-top-width:0px}
|
||||
.rn-borderRightWidth-fnigne{border-right-width:0px}
|
||||
.rn-borderBottomWidth-ndvcnb{border-bottom-width:0px}
|
||||
.rn-borderLeftWidth-gxnn5r{border-left-width:0px}
|
||||
.rn-boxSizing-deolkf{box-sizing:border-box}
|
||||
.rn-display-6koalj{display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex}
|
||||
.rn-flexShrink-1qe8dj5{-ms-flex-negative:0;-webkit-flex-shrink:0;flex-shrink:0}
|
||||
.rn-flexBasis-1mlwlqe{-ms-flex-preferred-size:auto;-webkit-flex-basis:auto;flex-basis:auto}
|
||||
.rn-flexDirection-eqz5dr{-ms-flex-direction:column;-webkit-box-direction:normal;-webkit-box-orient:vertical;-webkit-flex-direction:column;flex-direction:column}
|
||||
.rn-marginTop-1mnahxq{margin-top:0px}
|
||||
.rn-marginRight-61z16t{margin-right:0px}
|
||||
.rn-marginBottom-p1pxzi{margin-bottom:0px}
|
||||
.rn-marginLeft-11wrixw{margin-left:0px}
|
||||
.rn-minHeight-ifefl9{min-height:0px}
|
||||
.rn-minWidth-bcqeeo{min-width:0px}
|
||||
.rn-paddingTop-wk8lta{padding-top:0px}
|
||||
.rn-paddingRight-9aemit{padding-right:0px}
|
||||
.rn-paddingBottom-1mdbw0j{padding-bottom:0px}
|
||||
.rn-paddingLeft-gy4na3{padding-left:0px}
|
||||
.rn-position-bnwqim{position:relative}
|
||||
.rn-zIndex-1lgpqti{z-index:0}
|
||||
.rn-flexGrow-16y2uox{-ms-flex-positive:1;-webkit-box-flex:1;-webkit-flex-grow:1;flex-grow:1}
|
||||
.rn-flexShrink-1wbh5a2{-ms-flex-negative:1;-webkit-flex-shrink:1;flex-shrink:1}
|
||||
.rn-flexBasis-1ro0kt6{-ms-flex-preferred-size:0%;-webkit-flex-basis:0%;flex-basis:0%}"
|
||||
@@ -58,10 +35,20 @@ exports[`AppRegistry getApplication returns "element" and "getStyleElement" 1`]
|
||||
`;
|
||||
|
||||
exports[`AppRegistry getApplication returns "element" and "getStyleElement" 2`] = `
|
||||
"<style id=\\"react-native-stylesheet\\">@media all{
|
||||
"<style id=\\"react-native-stylesheet\\">:focus:not([data-rn-focusvisible-x92cna]){outline: none;}
|
||||
@media all{
|
||||
html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(0,0,0,0);}
|
||||
body{margin:0;}
|
||||
button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0;}
|
||||
input::-webkit-inner-spin-button,input::-webkit-outer-spin-button,input::-webkit-search-cancel-button,input::-webkit-search-decoration,input::-webkit-search-results-button,input::-webkit-search-results-decoration{display:none;}
|
||||
}</style>"
|
||||
.rn-reset{background-color:transparent;color:inherit;font:inherit;list-style:none;margin:0;text-align:inherit;text-decoration:none;}
|
||||
.rn-pointer{cursor:pointer;}
|
||||
}
|
||||
.rn-ui-textinput-11ngujt{-moz-appearance:textfield;-webkit-appearance:none;background-color:transparent;border-radius:0px;border:0 solid black;box-sizing:border-box;font-family:14px System;padding:0px;resize:none}
|
||||
.rn-ui-textSingleLine-1xjj19i{max-width:100%;overflow:hidden !important;text-overflow:ellipsis !important;white-space:nowrap !important}
|
||||
.rn-ui-textHasAncestor-z2plr{color:inherit;font:inherit;text-decoration:inherit;white-space:inherit}
|
||||
.rn-ui-textIsRoot-gw3a6r{color:black;font:normal 14px system-ui, -apple-system, BlinkMacSystemFont, \\"Segoe UI\\", Roboto, Ubuntu, \\"Helvetica Neue\\", sans-serif;text-decoration:none;white-space:pre-wrap}
|
||||
.rn-ui-text-1ntzlq4{background-color:transparent;border-width:0px;box-sizing:border-box;display:inline;margin:0px;padding:0px;text-align:inherit;word-wrap:break-word}
|
||||
.rn-ui-hitSlop-14nrb4u{bottom:0px;left:0px;position:absolute;right:0px;top:0px;z-index:-1}
|
||||
.rn-ui-view-15pvbv0{-ms-flex-align:stretch;-ms-flex-direction:column;-ms-flex-negative:0;-ms-flex-preferred-size:auto;-webkit-align-items:stretch;-webkit-box-align:stretch;-webkit-box-direction:normal;-webkit-box-orient:vertical;-webkit-flex-basis:auto;-webkit-flex-direction:column;-webkit-flex-shrink:0;align-items:stretch;background-color:transparent;border:0 solid black;box-sizing:border-box;color:inherit;display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex;flex-basis:auto;flex-direction:column;flex-shrink:0;font:inherit;list-style:none;margin:0px;min-height:0px;min-width:0px;padding:0px;position:relative;text-align:inherit;text-decoration:none;z-index:0}</style>"
|
||||
`;
|
||||
|
||||
@@ -15,9 +15,9 @@ import WebStyleSheet from './WebStyleSheet';
|
||||
const emptyObject = {};
|
||||
const STYLE_ELEMENT_ID = 'react-native-stylesheet';
|
||||
|
||||
const createClassName = (prop, value) => {
|
||||
const hashed = hash(prop + normalizeValue(value));
|
||||
return process.env.NODE_ENV !== 'production' ? `rn-${prop}-${hashed}` : `rn-${hashed}`;
|
||||
const createClassName = (name, value) => {
|
||||
const hashed = hash(name + normalizeValue(value));
|
||||
return process.env.NODE_ENV !== 'production' ? `rn-${name}-${hashed}` : `rn-${hashed}`;
|
||||
};
|
||||
|
||||
const normalizeValue = value => (typeof value === 'object' ? JSON.stringify(value) : value);
|
||||
@@ -69,6 +69,14 @@ export default class StyleSheetManager {
|
||||
return className;
|
||||
}
|
||||
|
||||
injectRule(name, body: string): void {
|
||||
const className = createClassName(`ui-${name}`, body);
|
||||
const rule = `.${className}{${body}}`;
|
||||
// insert after the reset + modality but before atomic css
|
||||
this._sheet.insertRuleOnce(rule, 2);
|
||||
return className;
|
||||
}
|
||||
|
||||
_addToCache(className, prop, value) {
|
||||
const cache = this._cache;
|
||||
if (!cache.byProp[prop]) {
|
||||
|
||||
@@ -103,10 +103,7 @@ StyleSheetValidation.addValidStylePropTypes({
|
||||
objectFit: oneOf(['fill', 'contain', 'cover', 'none', 'scale-down']),
|
||||
objectPosition: string,
|
||||
pointerEvents: string,
|
||||
tableLayout: string,
|
||||
/* @private */
|
||||
MozAppearance: string,
|
||||
WebkitAppearance: string
|
||||
tableLayout: string
|
||||
});
|
||||
|
||||
export default StyleSheetValidation;
|
||||
|
||||
@@ -30,12 +30,13 @@ export default class WebStyleSheet {
|
||||
}
|
||||
|
||||
if (domStyleElement) {
|
||||
modality(domStyleElement);
|
||||
// $FlowFixMe
|
||||
this._sheet = domStyleElement.sheet;
|
||||
this._textContent = domStyleElement.textContent;
|
||||
}
|
||||
}
|
||||
|
||||
modality((rule) => this.insertRuleOnce(rule, 0));
|
||||
}
|
||||
|
||||
containsRule(rule: string): boolean {
|
||||
@@ -49,7 +50,11 @@ export default class WebStyleSheet {
|
||||
insertRuleOnce(rule: string, position: ?number) {
|
||||
// Reduce chance of duplicate rules
|
||||
if (!this.containsRule(rule)) {
|
||||
this._cssRules.push(rule);
|
||||
if (position != null) {
|
||||
this._cssRules.splice(position, 0, rule);
|
||||
} else {
|
||||
this._cssRules.push(rule);
|
||||
}
|
||||
|
||||
// Check whether a rule was part of any prerendered styles (textContent
|
||||
// doesn't include styles injected via 'insertRule')
|
||||
|
||||
@@ -5,11 +5,14 @@ exports[`StyleSheet/StyleSheetManager getClassName 1`] = `undefined`;
|
||||
exports[`StyleSheet/StyleSheetManager getStyleSheet 1`] = `
|
||||
Object {
|
||||
"id": "react-native-stylesheet",
|
||||
"textContent": "@media all{
|
||||
"textContent": ":focus:not([data-rn-focusvisible-x92cna]){outline: none;}
|
||||
@media all{
|
||||
html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(0,0,0,0);}
|
||||
body{margin:0;}
|
||||
button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0;}
|
||||
input::-webkit-inner-spin-button,input::-webkit-outer-spin-button,input::-webkit-search-cancel-button,input::-webkit-search-decoration,input::-webkit-search-results-button,input::-webkit-search-results-decoration{display:none;}
|
||||
.rn-reset{background-color:transparent;color:inherit;font:inherit;list-style:none;margin:0;text-align:inherit;text-decoration:none;}
|
||||
.rn-pointer{cursor:pointer;}
|
||||
}
|
||||
.rn---test-property-ax3bxi{--test-property:test-value}",
|
||||
}
|
||||
|
||||
@@ -39,44 +39,17 @@ describe('StyleSheet/createReactDOMStyle', () => {
|
||||
expect(createReactDOMStyle(style)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe('borderWidth styles', () => {
|
||||
test('defaults to 0 when "null"', () => {
|
||||
expect(createReactDOMStyle({ borderWidth: null })).toEqual({
|
||||
borderTopWidth: '0px',
|
||||
borderRightWidth: '0px',
|
||||
borderBottomWidth: '0px',
|
||||
borderLeftWidth: '0px'
|
||||
});
|
||||
expect(createReactDOMStyle({ borderWidth: 2, borderRightWidth: null })).toEqual({
|
||||
borderTopWidth: '2px',
|
||||
borderRightWidth: '0px',
|
||||
borderBottomWidth: '2px',
|
||||
borderLeftWidth: '2px'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('flexbox styles', () => {
|
||||
test('flex defaults', () => {
|
||||
expect(createReactDOMStyle({ display: 'flex' })).toEqual({
|
||||
display: 'flex',
|
||||
flexShrink: 0,
|
||||
flexBasis: 'auto'
|
||||
});
|
||||
});
|
||||
|
||||
test('flex: -1', () => {
|
||||
expect(createReactDOMStyle({ display: 'flex', flex: -1 })).toEqual({
|
||||
display: 'flex',
|
||||
expect(createReactDOMStyle({ flex: -1 })).toEqual({
|
||||
flexBasis: 'auto',
|
||||
flexGrow: 0,
|
||||
flexShrink: 1,
|
||||
flexBasis: 'auto'
|
||||
flexShrink: 1
|
||||
});
|
||||
});
|
||||
|
||||
test('flex: 0', () => {
|
||||
expect(createReactDOMStyle({ display: 'flex', flex: 0 })).toEqual({
|
||||
display: 'flex',
|
||||
expect(createReactDOMStyle({ flex: 0 })).toEqual({
|
||||
flexGrow: 0,
|
||||
flexShrink: 0,
|
||||
flexBasis: '0%'
|
||||
@@ -84,8 +57,7 @@ describe('StyleSheet/createReactDOMStyle', () => {
|
||||
});
|
||||
|
||||
test('flex: 1', () => {
|
||||
expect(createReactDOMStyle({ display: 'flex', flex: 1 })).toEqual({
|
||||
display: 'flex',
|
||||
expect(createReactDOMStyle({ flex: 1 })).toEqual({
|
||||
flexGrow: 1,
|
||||
flexShrink: 1,
|
||||
flexBasis: '0%'
|
||||
@@ -93,8 +65,7 @@ describe('StyleSheet/createReactDOMStyle', () => {
|
||||
});
|
||||
|
||||
test('flex: 10', () => {
|
||||
expect(createReactDOMStyle({ display: 'flex', flex: 10 })).toEqual({
|
||||
display: 'flex',
|
||||
expect(createReactDOMStyle({ flex: 10 })).toEqual({
|
||||
flexGrow: 10,
|
||||
flexShrink: 1,
|
||||
flexBasis: '0%'
|
||||
@@ -103,15 +74,12 @@ describe('StyleSheet/createReactDOMStyle', () => {
|
||||
|
||||
test('flexBasis overrides', () => {
|
||||
// is flex-basis applied?
|
||||
expect(createReactDOMStyle({ display: 'flex', flexBasis: '25%' })).toEqual({
|
||||
display: 'flex',
|
||||
flexShrink: 0,
|
||||
expect(createReactDOMStyle({ flexBasis: '25%' })).toEqual({
|
||||
flexBasis: '25%'
|
||||
});
|
||||
|
||||
// can flex-basis override the 'flex' expansion?
|
||||
expect(createReactDOMStyle({ display: 'flex', flex: 1, flexBasis: '25%' })).toEqual({
|
||||
display: 'flex',
|
||||
expect(createReactDOMStyle({ flex: 1, flexBasis: '25%' })).toEqual({
|
||||
flexGrow: 1,
|
||||
flexShrink: 1,
|
||||
flexBasis: '25%'
|
||||
@@ -120,15 +88,12 @@ describe('StyleSheet/createReactDOMStyle', () => {
|
||||
|
||||
test('flexShrink overrides', () => {
|
||||
// is flex-shrink applied?
|
||||
expect(createReactDOMStyle({ display: 'flex', flexShrink: 1 })).toEqual({
|
||||
display: 'flex',
|
||||
flexShrink: 1,
|
||||
flexBasis: 'auto'
|
||||
expect(createReactDOMStyle({ flexShrink: 1 })).toEqual({
|
||||
flexShrink: 1
|
||||
});
|
||||
|
||||
// can flex-shrink override the 'flex' expansion?
|
||||
expect(createReactDOMStyle({ display: 'flex', flex: 1, flexShrink: 2 })).toEqual({
|
||||
display: 'flex',
|
||||
expect(createReactDOMStyle({ flex: 1, flexShrink: 2 })).toEqual({
|
||||
flexGrow: 1,
|
||||
flexShrink: 2,
|
||||
flexBasis: '0%'
|
||||
|
||||
13
packages/react-native-web/src/exports/StyleSheet/constants.js
vendored
Normal file
13
packages/react-native-web/src/exports/StyleSheet/constants.js
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Copyright (c) 2016-present, Nicolas Gallagher.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow strict
|
||||
*/
|
||||
|
||||
export const monospaceFontStack = 'monospace, monospace';
|
||||
|
||||
export const systemFontStack =
|
||||
'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, "Helvetica Neue", sans-serif';
|
||||
@@ -7,6 +7,7 @@
|
||||
* @noflow
|
||||
*/
|
||||
|
||||
import { monospaceFontStack, systemFontStack } from './constants';
|
||||
import normalizeColor from '../../modules/normalizeColor';
|
||||
import normalizeValue from './normalizeValue';
|
||||
import resolveShadowValue from './resolveShadowValue';
|
||||
@@ -54,18 +55,6 @@ const colorProps = {
|
||||
color: true
|
||||
};
|
||||
|
||||
const borderWidthProps = {
|
||||
borderWidth: true,
|
||||
borderTopWidth: true,
|
||||
borderRightWidth: true,
|
||||
borderBottomWidth: true,
|
||||
borderLeftWidth: true
|
||||
};
|
||||
|
||||
const monospaceFontStack = 'monospace, monospace';
|
||||
const systemFontStack =
|
||||
'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, "Helvetica Neue", sans-serif';
|
||||
|
||||
const alphaSortProps = propsArray =>
|
||||
propsArray.sort((a, b) => {
|
||||
if (a < b) {
|
||||
@@ -167,12 +156,6 @@ const createReducer = (style, styleProps) => {
|
||||
return (resolvedStyle, prop) => {
|
||||
let value = normalizeValue(prop, style[prop]);
|
||||
|
||||
// Make sure the default border width is explicitly set to '0' to avoid
|
||||
// falling back to any unwanted user-agent styles.
|
||||
if (borderWidthProps[prop]) {
|
||||
value = value == null ? normalizeValue(null, 0) : value;
|
||||
}
|
||||
|
||||
// Normalize color values
|
||||
if (colorProps[prop]) {
|
||||
value = normalizeColor(value);
|
||||
@@ -203,21 +186,6 @@ const createReducer = (style, styleProps) => {
|
||||
break;
|
||||
}
|
||||
|
||||
case 'display': {
|
||||
resolvedStyle.display = value;
|
||||
// A flex container in React Native has these defaults which should be
|
||||
// set only if there is no otherwise supplied flex style.
|
||||
if (style.display === 'flex' && style.flex == null) {
|
||||
if (style.flexShrink == null) {
|
||||
resolvedStyle.flexShrink = 0;
|
||||
}
|
||||
if (style.flexBasis == null) {
|
||||
resolvedStyle.flexBasis = 'auto';
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// The 'flex' property value in React Native must be a positive integer,
|
||||
// 0, or -1.
|
||||
case 'flex': {
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
*/
|
||||
|
||||
import hyphenateStyleName from 'hyphenate-style-name';
|
||||
import mapKeyValue from '../../modules/mapKeyValue';
|
||||
import normalizeValue from './normalizeValue';
|
||||
import prefixStyles from '../../modules/prefixStyles';
|
||||
|
||||
@@ -27,9 +26,15 @@ const createDeclarationString = (prop, val) => {
|
||||
* createRuleBlock({ width: 20, color: 'blue' });
|
||||
* // => 'color:blue;width:20px'
|
||||
*/
|
||||
const createRuleBlock = style =>
|
||||
mapKeyValue(prefixStyles(style), createDeclarationString)
|
||||
.sort()
|
||||
.join(';');
|
||||
const createRuleBlock = style => {
|
||||
const prefixedStyle = prefixStyles(style);
|
||||
return (
|
||||
Object.keys(prefixedStyle)
|
||||
.map(prop => createDeclarationString(prop, prefixedStyle[prop]))
|
||||
// put short-form and vendor prefixed properties first
|
||||
.sort()
|
||||
.join(';')
|
||||
);
|
||||
};
|
||||
|
||||
export default createRuleBlock;
|
||||
|
||||
53
packages/react-native-web/src/exports/StyleSheet/css.js
vendored
Normal file
53
packages/react-native-web/src/exports/StyleSheet/css.js
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Copyright (c) 2016-present, Nicolas Gallagher.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @noflow
|
||||
*/
|
||||
|
||||
import createRuleBlock from './createRuleBlock';
|
||||
import styleResolver from './styleResolver';
|
||||
import { systemFontStack } from './constants';
|
||||
|
||||
const fontFamilyProperties = ['font', 'fontFamily'];
|
||||
/**
|
||||
* A simple (and dangerous) CSS system.
|
||||
* The order of CSS rule insertion is not guaranteed.
|
||||
* Avoiding combining 2 or more classes that modify the same property.
|
||||
*/
|
||||
const css = {
|
||||
/**
|
||||
* const classes = css.create({ base: {}, extra: {} })
|
||||
*/
|
||||
create(rules) {
|
||||
const result = {};
|
||||
Object.keys(rules).forEach(key => {
|
||||
const rule = rules[key];
|
||||
fontFamilyProperties.forEach(prop => {
|
||||
const value = rule[prop];
|
||||
if (value && value.indexOf('System') > -1) {
|
||||
rule[prop] = value.replace('System', systemFontStack);
|
||||
}
|
||||
});
|
||||
const cssRule = createRuleBlock(rule);
|
||||
const className = styleResolver.styleSheetManager.injectRule(key, cssRule);
|
||||
result[key] = className;
|
||||
});
|
||||
return result;
|
||||
},
|
||||
/**
|
||||
* css.combine(classes.base, classes.extra)
|
||||
*/
|
||||
combine(...args) {
|
||||
return args.reduce((className, value) => {
|
||||
if (value) {
|
||||
className += className.length > 0 ? ' ' + value : value;
|
||||
}
|
||||
return className;
|
||||
}, '');
|
||||
}
|
||||
};
|
||||
|
||||
export default css;
|
||||
@@ -12,13 +12,34 @@ const safeRule = rule => `@media all{\n${rule}\n}`;
|
||||
|
||||
const resets = [
|
||||
// minimal top-level reset
|
||||
'html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(0,0,0,0);}',
|
||||
'html{' +
|
||||
'-ms-text-size-adjust:100%;' +
|
||||
'-webkit-text-size-adjust:100%;' +
|
||||
'-webkit-tap-highlight-color:rgba(0,0,0,0);' +
|
||||
'}',
|
||||
'body{margin:0;}',
|
||||
// minimal form pseudo-element reset
|
||||
'button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0;}',
|
||||
'input::-webkit-inner-spin-button,input::-webkit-outer-spin-button,' +
|
||||
'input::-webkit-search-cancel-button,input::-webkit-search-decoration,' +
|
||||
'input::-webkit-search-results-button,input::-webkit-search-results-decoration{display:none;}'
|
||||
'input::-webkit-inner-spin-button,' +
|
||||
'input::-webkit-outer-spin-button,' +
|
||||
'input::-webkit-search-cancel-button,' +
|
||||
'input::-webkit-search-decoration,' +
|
||||
'input::-webkit-search-results-button,' +
|
||||
'input::-webkit-search-results-decoration{' +
|
||||
'display:none;' +
|
||||
'}',
|
||||
// Reset styles for heading, link, and list DOM elements
|
||||
'.rn-reset{' +
|
||||
'background-color:transparent;' +
|
||||
'color:inherit;' +
|
||||
'font:inherit;' +
|
||||
'list-style:none;' +
|
||||
'margin:0;' +
|
||||
'text-align:inherit;' +
|
||||
'text-decoration:none;' +
|
||||
'}',
|
||||
// For pressable elements
|
||||
'.rn-pointer{cursor:pointer;}'
|
||||
];
|
||||
|
||||
const reset = [safeRule(resets.join('\n'))];
|
||||
|
||||
@@ -28,7 +28,9 @@ const focusVisibleAttributeName =
|
||||
|
||||
const rule = `:focus:not([${focusVisibleAttributeName}]){outline: none;}`;
|
||||
|
||||
const modality = styleElement => {
|
||||
const modality = insertRule => {
|
||||
insertRule(rule);
|
||||
|
||||
if (!canUseDOM) {
|
||||
return;
|
||||
}
|
||||
@@ -264,8 +266,6 @@ const modality = styleElement => {
|
||||
removeInitialPointerMoveListeners();
|
||||
}
|
||||
|
||||
styleElement.sheet.insertRule(rule, 0);
|
||||
|
||||
document.addEventListener('keydown', onKeyDown, true);
|
||||
document.addEventListener('mousedown', onPointerDown, true);
|
||||
document.addEventListener('pointerdown', onPointerDown, true);
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`components/Text prop "children" 1`] = `
|
||||
<span
|
||||
class="rn-ui-text-1ntzlq4 rn-ui-textHasAncestor-z2plr"
|
||||
data-testid="child"
|
||||
dir="auto"
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`components/Text prop "onPress" 1`] = `
|
||||
<div
|
||||
className="rn-borderTopWidth-13yce4e rn-borderRightWidth-fnigne rn-borderBottomWidth-ndvcnb rn-borderLeftWidth-gxnn5r rn-boxSizing-deolkf rn-color-homxoj rn-cursor-1loqt21 rn-display-1471scf rn-fontFamily-14xgk7a rn-fontSize-1b43r93 rn-fontStyle-o11vmf rn-fontVariant-ebii48 rn-fontWeight-gul640 rn-lineHeight-t9a87b rn-marginTop-1mnahxq rn-marginRight-61z16t rn-marginBottom-p1pxzi rn-marginLeft-11wrixw rn-paddingTop-wk8lta rn-paddingRight-9aemit rn-paddingBottom-1mdbw0j rn-paddingLeft-gy4na3 rn-textDecoration-bauka4 rn-whiteSpace-q42fyq rn-wordWrap-qvutc0"
|
||||
className="rn-ui-text-1ntzlq4 rn-ui-textIsRoot-gw3a6r rn-cursor-1loqt21"
|
||||
data-focusable={true}
|
||||
dir="auto"
|
||||
onClick={[Function]}
|
||||
@@ -13,14 +21,14 @@ exports[`components/Text prop "onPress" 1`] = `
|
||||
|
||||
exports[`components/Text prop "selectable" 1`] = `
|
||||
<div
|
||||
className="rn-borderTopWidth-13yce4e rn-borderRightWidth-fnigne rn-borderBottomWidth-ndvcnb rn-borderLeftWidth-gxnn5r rn-boxSizing-deolkf rn-color-homxoj rn-display-1471scf rn-fontFamily-14xgk7a rn-fontSize-1b43r93 rn-fontStyle-o11vmf rn-fontVariant-ebii48 rn-fontWeight-gul640 rn-lineHeight-t9a87b rn-marginTop-1mnahxq rn-marginRight-61z16t rn-marginBottom-p1pxzi rn-marginLeft-11wrixw rn-paddingTop-wk8lta rn-paddingRight-9aemit rn-paddingBottom-1mdbw0j rn-paddingLeft-gy4na3 rn-textDecoration-bauka4 rn-whiteSpace-q42fyq rn-wordWrap-qvutc0"
|
||||
className="rn-ui-text-1ntzlq4 rn-ui-textIsRoot-gw3a6r"
|
||||
dir="auto"
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`components/Text prop "selectable" 2`] = `
|
||||
<div
|
||||
className="rn-borderTopWidth-13yce4e rn-borderRightWidth-fnigne rn-borderBottomWidth-ndvcnb rn-borderLeftWidth-gxnn5r rn-boxSizing-deolkf rn-color-homxoj rn-display-1471scf rn-fontFamily-14xgk7a rn-fontSize-1b43r93 rn-fontStyle-o11vmf rn-fontVariant-ebii48 rn-fontWeight-gul640 rn-lineHeight-t9a87b rn-marginTop-1mnahxq rn-marginRight-61z16t rn-marginBottom-p1pxzi rn-marginLeft-11wrixw rn-paddingTop-wk8lta rn-paddingRight-9aemit rn-paddingBottom-1mdbw0j rn-paddingLeft-gy4na3 rn-textDecoration-bauka4 rn-userSelect-lrvibr rn-whiteSpace-q42fyq rn-wordWrap-qvutc0"
|
||||
className="rn-ui-text-1ntzlq4 rn-ui-textIsRoot-gw3a6r rn-userSelect-lrvibr"
|
||||
dir="auto"
|
||||
/>
|
||||
`;
|
||||
|
||||
@@ -29,9 +29,9 @@ describe('components/Text', () => {
|
||||
});
|
||||
|
||||
test('prop "children"', () => {
|
||||
const children = <Text testID="1" />;
|
||||
const component = shallow(<Text children={children} />);
|
||||
expect(component.contains(children)).toEqual(true);
|
||||
const children = <Text testID="child" />;
|
||||
const component = render(<Text children={children} />);
|
||||
expect(component.children()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('prop "numberOfLines"', () => {});
|
||||
|
||||
@@ -13,6 +13,7 @@ import applyNativeMethods from '../../modules/applyNativeMethods';
|
||||
import { bool } from 'prop-types';
|
||||
import { Component } from 'react';
|
||||
import createElement from '../createElement';
|
||||
import css from '../StyleSheet/css';
|
||||
import StyleSheet from '../StyleSheet';
|
||||
import TextPropTypes from './TextPropTypes';
|
||||
|
||||
@@ -57,7 +58,7 @@ class Text extends Component<*> {
|
||||
...otherProps
|
||||
} = this.props;
|
||||
|
||||
const { isInAParentText } = this.context;
|
||||
const { isInAParentText: hasTextAncestor } = this.context;
|
||||
|
||||
if (onPress) {
|
||||
otherProps.accessible = true;
|
||||
@@ -65,18 +66,20 @@ class Text extends Component<*> {
|
||||
otherProps.onKeyDown = this._createEnterHandler(onPress);
|
||||
}
|
||||
|
||||
// allow browsers to automatically infer the language writing direction
|
||||
otherProps.dir = dir !== undefined ? dir : 'auto';
|
||||
otherProps.className = css.combine(
|
||||
classes.text,
|
||||
hasTextAncestor ? classes.textHasAncestor : classes.textIsRoot,
|
||||
numberOfLines === 1 && classes.textSingleLine
|
||||
);
|
||||
otherProps.style = [
|
||||
styles.initial,
|
||||
this.context.isInAParentText === true && styles.isInAParentText,
|
||||
style,
|
||||
selectable === false && styles.notSelectable,
|
||||
numberOfLines === 1 && styles.singleLineStyle,
|
||||
onPress && styles.pressable
|
||||
];
|
||||
// allow browsers to automatically infer the language writing direction
|
||||
otherProps.dir = dir !== undefined ? dir : 'auto';
|
||||
|
||||
const component = isInAParentText ? 'span' : 'div';
|
||||
const component = hasTextAncestor ? 'span' : 'div';
|
||||
|
||||
return createElement(component, otherProps);
|
||||
}
|
||||
@@ -97,41 +100,45 @@ class Text extends Component<*> {
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
initial: {
|
||||
const classes = css.create({
|
||||
text: {
|
||||
backgroundColor: 'transparent',
|
||||
borderWidth: 0,
|
||||
boxSizing: 'border-box',
|
||||
color: 'inherit',
|
||||
display: 'inline',
|
||||
fontFamily: 'System',
|
||||
fontSize: 14,
|
||||
fontStyle: 'inherit',
|
||||
fontVariant: ['inherit'],
|
||||
fontWeight: 'inherit',
|
||||
lineHeight: 'inherit',
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
textDecorationLine: 'none',
|
||||
whiteSpace: 'pre-wrap',
|
||||
textAlign: 'inherit',
|
||||
wordWrap: 'break-word'
|
||||
},
|
||||
isInAParentText: {
|
||||
// inherit parent font styles
|
||||
fontFamily: 'inherit',
|
||||
fontSize: 'inherit',
|
||||
textIsRoot: {
|
||||
color: 'black',
|
||||
font: 'normal 14px System',
|
||||
textDecoration: 'none',
|
||||
whiteSpace: 'pre-wrap'
|
||||
},
|
||||
textHasAncestor: {
|
||||
color: 'inherit',
|
||||
font: 'inherit',
|
||||
textDecoration: 'inherit',
|
||||
whiteSpace: 'inherit'
|
||||
},
|
||||
// "!important" is used to prevent essential styles from being overridden
|
||||
// by merged styles
|
||||
textSingleLine: {
|
||||
maxWidth: '100%',
|
||||
overflow: 'hidden !important',
|
||||
textOverflow: 'ellipsis !important',
|
||||
whiteSpace: 'nowrap !important'
|
||||
}
|
||||
});
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
notSelectable: {
|
||||
userSelect: 'none'
|
||||
},
|
||||
pressable: {
|
||||
cursor: 'pointer'
|
||||
},
|
||||
singleLineStyle: {
|
||||
maxWidth: '100%',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap'
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -14,8 +14,8 @@ import { canUseDOM } from 'fbjs/lib/ExecutionEnvironment';
|
||||
import { Component } from 'react';
|
||||
import ColorPropType from '../ColorPropType';
|
||||
import createElement from '../createElement';
|
||||
import css from '../StyleSheet/css';
|
||||
import findNodeHandle from '../findNodeHandle';
|
||||
import StyleSheet from '../StyleSheet';
|
||||
import StyleSheetPropType from '../../modules/StyleSheetPropType';
|
||||
import TextInputStylePropTypes from './TextInputStylePropTypes';
|
||||
import TextInputState from '../../modules/TextInputState';
|
||||
@@ -254,6 +254,7 @@ class TextInput extends Component<*> {
|
||||
|
||||
Object.assign(otherProps, {
|
||||
autoCorrect: autoCorrect ? 'on' : 'off',
|
||||
className: classes.textinput,
|
||||
dir: 'auto',
|
||||
onBlur: normalizeEventHandler(this._handleBlur),
|
||||
onChange: normalizeEventHandler(this._handleChange),
|
||||
@@ -264,7 +265,7 @@ class TextInput extends Component<*> {
|
||||
readOnly: !editable,
|
||||
ref: this._setNode,
|
||||
spellCheck: spellCheck != null ? spellCheck : autoCorrect,
|
||||
style: [styles.initial, style]
|
||||
style
|
||||
});
|
||||
|
||||
if (multiline) {
|
||||
@@ -388,18 +389,15 @@ class TextInput extends Component<*> {
|
||||
};
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
initial: {
|
||||
const classes = css.create({
|
||||
textinput: {
|
||||
MozAppearance: 'textfield',
|
||||
WebkitAppearance: 'none',
|
||||
backgroundColor: 'transparent',
|
||||
borderColor: 'black',
|
||||
border: '0 solid black',
|
||||
borderRadius: 0,
|
||||
borderStyle: 'solid',
|
||||
borderWidth: 0,
|
||||
boxSizing: 'border-box',
|
||||
fontFamily: 'System',
|
||||
fontSize: 14,
|
||||
fontFamily: '14px System',
|
||||
padding: 0,
|
||||
resize: 'none'
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import applyLayout from '../../modules/applyLayout';
|
||||
import applyNativeMethods from '../../modules/applyNativeMethods';
|
||||
import { bool } from 'prop-types';
|
||||
import createElement from '../createElement';
|
||||
import css from '../StyleSheet/css';
|
||||
import filterSupportedProps from './filterSupportedProps';
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
import StyleSheet from '../StyleSheet';
|
||||
@@ -51,14 +52,18 @@ class View extends Component<ViewProps> {
|
||||
|
||||
const { isInAParentText } = this.context;
|
||||
|
||||
supportedProps.className = classes.view;
|
||||
supportedProps.style = StyleSheet.compose(
|
||||
styles.initial,
|
||||
StyleSheet.compose(isInAParentText && styles.inline, this.props.style)
|
||||
isInAParentText && styles.inline,
|
||||
this.props.style
|
||||
);
|
||||
|
||||
if (hitSlop) {
|
||||
const hitSlopStyle = calculateHitSlopStyle(hitSlop);
|
||||
const hitSlopChild = createElement('span', { style: [styles.hitSlop, hitSlopStyle] });
|
||||
const hitSlopChild = createElement('span', {
|
||||
className: classes.hitslop,
|
||||
style: hitSlopStyle
|
||||
});
|
||||
supportedProps.children = React.Children.toArray([hitSlopChild, supportedProps.children]);
|
||||
}
|
||||
|
||||
@@ -66,32 +71,45 @@ class View extends Component<ViewProps> {
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
// https://github.com/facebook/css-layout#default-values
|
||||
initial: {
|
||||
const classes = css.create({
|
||||
view: {
|
||||
alignItems: 'stretch',
|
||||
borderWidth: 0,
|
||||
borderStyle: 'solid',
|
||||
border: '0 solid black',
|
||||
boxSizing: 'border-box',
|
||||
display: 'flex',
|
||||
flexBasis: 'auto',
|
||||
flexDirection: 'column',
|
||||
flexShrink: 0,
|
||||
margin: 0,
|
||||
minHeight: 0,
|
||||
minWidth: 0,
|
||||
padding: 0,
|
||||
position: 'relative',
|
||||
zIndex: 0,
|
||||
// fix flexbox bugs
|
||||
minHeight: 0,
|
||||
minWidth: 0
|
||||
},
|
||||
inline: {
|
||||
display: 'inline-flex'
|
||||
// resets for if View is rendered as a link or list DOM element
|
||||
backgroundColor: 'transparent',
|
||||
color: 'inherit',
|
||||
font: 'inherit',
|
||||
listStyle: 'none',
|
||||
textAlign: 'inherit',
|
||||
textDecoration: 'none'
|
||||
},
|
||||
// this zIndex-ordering positions the hitSlop above the View but behind
|
||||
// its children
|
||||
hitSlop: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
zIndex: -1
|
||||
}
|
||||
});
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
inline: {
|
||||
display: 'inline-flex'
|
||||
}
|
||||
});
|
||||
|
||||
export default applyLayout(applyNativeMethods(View));
|
||||
|
||||
@@ -2,10 +2,14 @@
|
||||
|
||||
exports[`modules/createDOMProps includes "rel" values for "a" elements (to securely open external links) 1`] = `" noopener noreferrer"`;
|
||||
|
||||
exports[`modules/createDOMProps includes cursor style for "button" role 1`] = `"rn-cursor-1loqt21"`;
|
||||
exports[`modules/createDOMProps includes base reset style for browser-styled elements 1`] = `"rn-reset"`;
|
||||
|
||||
exports[`modules/createDOMProps includes reset styles for "a" elements 1`] = `"rn-backgroundColor-1niwhzg rn-color-homxoj rn-textDecoration-bauka4"`;
|
||||
exports[`modules/createDOMProps includes base reset style for browser-styled elements 2`] = `"rn-reset"`;
|
||||
|
||||
exports[`modules/createDOMProps includes reset styles for "button" elements 1`] = `"rn-appearance-30o5oe rn-backgroundColor-1niwhzg rn-color-homxoj rn-fontFamily-poiln3 rn-fontSize-7cikom rn-fontStyle-o11vmf rn-fontVariant-ebii48 rn-fontWeight-gul640 rn-lineHeight-t9a87b rn-textAlign-1ttztb7"`;
|
||||
exports[`modules/createDOMProps includes base reset style for browser-styled elements 3`] = `"rn-reset"`;
|
||||
|
||||
exports[`modules/createDOMProps includes reset styles for "ul" elements 1`] = `"rn-listStyle-1ebb2ja"`;
|
||||
exports[`modules/createDOMProps includes base reset style for browser-styled elements 4`] = `"rn-reset"`;
|
||||
|
||||
exports[`modules/createDOMProps includes cursor style for pressable roles 1`] = `"rn-pointer"`;
|
||||
|
||||
exports[`modules/createDOMProps includes cursor style for pressable roles 2`] = `"rn-pointer"`;
|
||||
|
||||
@@ -193,23 +193,15 @@ describe('modules/createDOMProps', () => {
|
||||
expect(props.rel).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('includes reset styles for "a" elements', () => {
|
||||
const props = createDOMProps('a');
|
||||
expect(props.className).toMatchSnapshot();
|
||||
test('includes cursor style for pressable roles', () => {
|
||||
expect(createDOMProps('span', { accessibilityRole: 'link' }).className).toMatchSnapshot();
|
||||
expect(createDOMProps('span', { accessibilityRole: 'button' }).className).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('includes reset styles for "button" elements', () => {
|
||||
const props = createDOMProps('button');
|
||||
expect(props.className).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('includes cursor style for "button" role', () => {
|
||||
const props = createDOMProps('span', { accessibilityRole: 'button' });
|
||||
expect(props.className).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('includes reset styles for "ul" elements', () => {
|
||||
const props = createDOMProps('ul');
|
||||
expect(props.className).toMatchSnapshot();
|
||||
test('includes base reset style for browser-styled elements', () => {
|
||||
expect(createDOMProps('a').className).toMatchSnapshot();
|
||||
expect(createDOMProps('button').className).toMatchSnapshot();
|
||||
expect(createDOMProps('li').className).toMatchSnapshot();
|
||||
expect(createDOMProps('ul').className).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -13,40 +13,6 @@ import styleResolver from '../../exports/StyleSheet/styleResolver';
|
||||
|
||||
const emptyObject = {};
|
||||
|
||||
const resetStyles = StyleSheet.create({
|
||||
ariaButton: {
|
||||
cursor: 'pointer'
|
||||
},
|
||||
button: {
|
||||
appearance: 'none',
|
||||
backgroundColor: 'transparent',
|
||||
color: 'inherit',
|
||||
fontFamily: 'inherit',
|
||||
fontSize: 'inherit',
|
||||
fontStyle: 'inherit',
|
||||
fontVariant: ['inherit'],
|
||||
fontWeight: 'inherit',
|
||||
lineHeight: 'inherit',
|
||||
textAlign: 'inherit'
|
||||
},
|
||||
heading: {
|
||||
fontFamily: 'inherit',
|
||||
fontSize: 'inherit',
|
||||
fontStyle: 'inherit',
|
||||
fontVariant: ['inherit'],
|
||||
fontWeight: 'inherit',
|
||||
lineHeight: 'inherit'
|
||||
},
|
||||
link: {
|
||||
backgroundColor: 'transparent',
|
||||
color: 'inherit',
|
||||
textDecorationLine: 'none'
|
||||
},
|
||||
list: {
|
||||
listStyle: 'none'
|
||||
}
|
||||
});
|
||||
|
||||
const pointerEventsStyles = StyleSheet.create({
|
||||
auto: {
|
||||
pointerEvents: 'auto'
|
||||
@@ -129,6 +95,8 @@ const createDOMProps = (component, props, styleResolver) => {
|
||||
importantForAccessibility !== 'no-hide-descendants';
|
||||
if (
|
||||
role === 'link' ||
|
||||
component === 'a' ||
|
||||
component === 'button' ||
|
||||
component === 'input' ||
|
||||
component === 'select' ||
|
||||
component === 'textarea'
|
||||
@@ -152,30 +120,53 @@ const createDOMProps = (component, props, styleResolver) => {
|
||||
|
||||
// STYLE
|
||||
// Resolve React Native styles to optimized browser equivalent
|
||||
const reactNativeStyle = [
|
||||
component === 'a' && resetStyles.link,
|
||||
component === 'button' && resetStyles.button,
|
||||
role === 'heading' && resetStyles.heading,
|
||||
component === 'ul' && resetStyles.list,
|
||||
role === 'button' && !disabled && resetStyles.ariaButton,
|
||||
const reactNativeStyle = StyleSheet.compose(
|
||||
pointerEvents && pointerEventsStyles[pointerEvents],
|
||||
providedStyle,
|
||||
placeholderTextColor && { placeholderTextColor }
|
||||
];
|
||||
StyleSheet.compose(
|
||||
providedStyle,
|
||||
placeholderTextColor && { placeholderTextColor }
|
||||
)
|
||||
);
|
||||
const { className, style } = styleResolver(reactNativeStyle);
|
||||
if (className && className.constructor === String) {
|
||||
domProps.className = props.className ? `${props.className} ${className}` : className;
|
||||
}
|
||||
if (style) {
|
||||
domProps.style = style;
|
||||
}
|
||||
|
||||
// CLASSNAME
|
||||
// Apply static style resets
|
||||
let c;
|
||||
// style interactive elements for mouse and mobile browsers
|
||||
if ((role === 'button' || role === 'link') && !disabled) {
|
||||
c = 'rn-pointer';
|
||||
}
|
||||
// style reset various elements (not all are used internally)
|
||||
if (
|
||||
component === 'a' ||
|
||||
component === 'button' ||
|
||||
component === 'li' ||
|
||||
component === 'ul' ||
|
||||
role === 'heading'
|
||||
) {
|
||||
c = 'rn-reset' + (c != null ? ' ' + c : '');
|
||||
}
|
||||
// style from createElement use
|
||||
if (props.className != null) {
|
||||
c = props.className + (c != null ? ' ' + c : '');
|
||||
}
|
||||
// style from React Native StyleSheets
|
||||
if (className != null && className !== '') {
|
||||
c = (c != null ? c + ' ' : '') + className;
|
||||
}
|
||||
if (c != null) {
|
||||
domProps.className = c;
|
||||
}
|
||||
|
||||
// OTHER
|
||||
// Native element ID
|
||||
if (nativeID && nativeID.constructor === String) {
|
||||
domProps.id = nativeID;
|
||||
}
|
||||
// Link security and automation test ids
|
||||
// Link security
|
||||
if (component === 'a' && domProps.target === '_blank') {
|
||||
domProps.rel = `${domProps.rel || ''} noopener noreferrer`;
|
||||
}
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
const hasOwnProperty = Object.prototype.hasOwnProperty;
|
||||
|
||||
const mapKeyValue = (obj, fn) => {
|
||||
const result = [];
|
||||
for (const key in obj) {
|
||||
if (hasOwnProperty.call(obj, key)) {
|
||||
const r = fn(key, obj[key]);
|
||||
r && result.push(r);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
export default mapKeyValue;
|
||||
Reference in New Issue
Block a user