From 092d5d12f7d0d15850acb6444fb1c473ab7287ef Mon Sep 17 00:00:00 2001 From: Nicolas Gallagher Date: Wed, 26 Jul 2017 15:46:18 -0700 Subject: [PATCH] [fix] unitless values for vendor prefixed properties Problem: Numeric values are suffixed with 'px', unless the property supports unitless values. However, vendor prefixed properties were ignored resulting in invalid CSS values for properties like '-webkit-flex-shrink'. Solution: Apply the upstream solution from React, which includes vendor prefixed properties in the "unitless number" map. Also build a custom vendor prefixer to ensure adequate browser support (i.e., Safari 7 and older Chrome). --- package.json | 1 + scripts/createPrefixer.js | 21 +++ src/apis/StyleSheet/StyleManager.js | 3 +- .../StyleSheet/__tests__/StyleManager-test.js | 8 + src/modules/normalizeNativeEvent/index.js | 4 +- src/modules/prefixStyles/index.js | 5 +- src/modules/prefixStyles/static.js | 155 ++++++++++++++++++ src/modules/unitlessNumbers/index.js | 14 ++ yarn.lock | 32 ++++ 9 files changed, 239 insertions(+), 4 deletions(-) create mode 100644 scripts/createPrefixer.js create mode 100644 src/modules/prefixStyles/static.js diff --git a/package.json b/package.json index 26f2af02..c9f6d9d1 100644 --- a/package.json +++ b/package.json @@ -74,6 +74,7 @@ "babel-loader": "^7.1.1", "babel-plugin-transform-react-remove-prop-types": "^0.4.6", "babel-preset-react-native": "^2.1.0", + "caniuse-api": "^2.0.0", "del-cli": "^1.1.0", "enzyme": "^2.9.1", "enzyme-to-json": "^1.5.1", diff --git a/scripts/createPrefixer.js b/scripts/createPrefixer.js new file mode 100644 index 00000000..131c736c --- /dev/null +++ b/scripts/createPrefixer.js @@ -0,0 +1,21 @@ +const generateData = require('inline-style-prefixer/generator'); +const path = require('path'); + +const browserList = { + chrome: 38, + android: 4, + firefox: 40, + ios_saf: 7, + safari: 7, + ie: 11, + ie_mob: 11, + edge: 12, + opera: 16, + op_mini: 12, + and_uc: 9, + and_chr: 38 +}; + +generateData(browserList, { + staticPath: path.join(__dirname, '../src/modules/prefixStyles/static.js'), +}); diff --git a/src/apis/StyleSheet/StyleManager.js b/src/apis/StyleSheet/StyleManager.js index 33bdeba4..88eeb02d 100644 --- a/src/apis/StyleSheet/StyleManager.js +++ b/src/apis/StyleSheet/StyleManager.js @@ -113,7 +113,8 @@ export default class StyleManager { if (prop !== 'pointerEvents') { Object.keys(cache[prop]).forEach(value => { const className = this.getClassName(prop, value); - rules.push(createCssRule(className, prop, value)); + const rule = createCssRule(className, prop, value); + rules.push(rule); }); } return rules; diff --git a/src/apis/StyleSheet/__tests__/StyleManager-test.js b/src/apis/StyleSheet/__tests__/StyleManager-test.js index ae7f2df4..dda015a8 100644 --- a/src/apis/StyleSheet/__tests__/StyleManager-test.js +++ b/src/apis/StyleSheet/__tests__/StyleManager-test.js @@ -28,4 +28,12 @@ describe('apis/StyleSheet/StyleManager', () => { styleManager.setDeclaration('width', '100px'); expect(styleManager.getStyleSheetHtml()).toMatchSnapshot(); }); + + test('setDeclaration', () => { + styleManager.mainSheet.sheet.insertRule = (rule, position) => { + // check for regressions in CSS write path (e.g., 0 => 0px) + expect(rule.indexOf('-webkit-flex-shrink:0;')).not.toEqual(-1); + }; + styleManager.setDeclaration('flexShrink', 0); + }); }); diff --git a/src/modules/normalizeNativeEvent/index.js b/src/modules/normalizeNativeEvent/index.js index f9b624b9..fce8bda5 100644 --- a/src/modules/normalizeNativeEvent/index.js +++ b/src/modules/normalizeNativeEvent/index.js @@ -103,8 +103,8 @@ function normalizeMouseEvent(nativeEvent) { identifier: touches[0].identifier, locationX: nativeEvent.offsetX, locationY: nativeEvent.offsetY, - offsetX: nativeEvent.offsetX, - offsetY: nativeEvent.offsetY, + offsetX: nativeEvent.offsetX, + offsetY: nativeEvent.offsetY, pageX: nativeEvent.pageX, pageY: nativeEvent.pageY, preventDefault: nativeEvent.preventDefault.bind(nativeEvent), diff --git a/src/modules/prefixStyles/index.js b/src/modules/prefixStyles/index.js index 36f5bb4b..d3b47440 100644 --- a/src/modules/prefixStyles/index.js +++ b/src/modules/prefixStyles/index.js @@ -8,7 +8,10 @@ * @flow */ -import prefixAll from 'inline-style-prefixer/static'; +import createPrefixer from 'inline-style-prefixer/static/createPrefixer'; +import staticData from './static'; + +const prefixAll = createPrefixer(staticData); export default prefixAll; diff --git a/src/modules/prefixStyles/static.js b/src/modules/prefixStyles/static.js new file mode 100644 index 00000000..9ea0cecc --- /dev/null +++ b/src/modules/prefixStyles/static.js @@ -0,0 +1,155 @@ +import crossFade from 'inline-style-prefixer/static/plugins/crossFade'; +import cursor from 'inline-style-prefixer/static/plugins/cursor'; +import filter from 'inline-style-prefixer/static/plugins/filter'; +import flex from 'inline-style-prefixer/static/plugins/flex'; +import flexboxOld from 'inline-style-prefixer/static/plugins/flexboxOld'; +import gradient from 'inline-style-prefixer/static/plugins/gradient'; +import imageSet from 'inline-style-prefixer/static/plugins/imageSet'; +import position from 'inline-style-prefixer/static/plugins/position'; +import sizing from 'inline-style-prefixer/static/plugins/sizing'; +import transition from 'inline-style-prefixer/static/plugins/transition'; +const w = ['Webkit']; +const m = ['Moz']; +const ms = ['ms']; +const wm = ['Webkit', 'Moz']; +const wms = ['Webkit', 'ms']; +const wmms = ['Webkit', 'Moz', 'ms']; + +export default { + plugins: [ + crossFade, + cursor, + filter, + flex, + flexboxOld, + gradient, + imageSet, + position, + sizing, + transition + ], + prefixMap: { + animation: w, + animationDelay: w, + animationDirection: w, + animationFillMode: w, + animationDuration: w, + animationIterationCount: w, + animationName: w, + animationPlayState: w, + animationTimingFunction: w, + appearance: wm, + userSelect: wmms, + textEmphasisPosition: w, + textEmphasis: w, + textEmphasisStyle: w, + textEmphasisColor: w, + boxDecorationBreak: w, + clipPath: w, + maskImage: w, + maskMode: w, + maskRepeat: w, + maskPosition: w, + maskClip: w, + maskOrigin: w, + maskSize: w, + maskComposite: w, + mask: w, + maskBorderSource: w, + maskBorderMode: w, + maskBorderSlice: w, + maskBorderWidth: w, + maskBorderOutset: w, + maskBorderRepeat: w, + maskBorder: w, + maskType: w, + textDecorationStyle: w, + textDecorationSkip: w, + textDecorationLine: w, + textDecorationColor: w, + filter: w, + fontFeatureSettings: w, + breakAfter: wmms, + breakBefore: wmms, + breakInside: wmms, + columnCount: wm, + columnFill: wm, + columnGap: wm, + columnRule: wm, + columnRuleColor: wm, + columnRuleStyle: wm, + columnRuleWidth: wm, + columns: wm, + columnSpan: wm, + columnWidth: wm, + flex: w, + flexBasis: w, + flexDirection: w, + flexGrow: w, + flexFlow: w, + flexShrink: w, + flexWrap: w, + alignContent: w, + alignItems: w, + alignSelf: w, + justifyContent: w, + order: w, + transform: w, + transformOrigin: w, + transformOriginX: w, + transformOriginY: w, + backfaceVisibility: w, + perspective: w, + perspectiveOrigin: w, + transformStyle: w, + transformOriginZ: w, + backdropFilter: w, + fontKerning: w, + scrollSnapType: wms, + scrollSnapPointsX: wms, + scrollSnapPointsY: wms, + scrollSnapDestination: wms, + scrollSnapCoordinate: wms, + shapeImageThreshold: w, + shapeImageMargin: w, + shapeImageOutside: w, + hyphens: wmms, + flowInto: wms, + flowFrom: wms, + regionFragment: wms, + textAlignLast: m, + tabSize: m, + wrapFlow: ms, + wrapThrough: ms, + wrapMargin: ms, + gridTemplateColumns: ms, + gridTemplateRows: ms, + gridTemplateAreas: ms, + gridTemplate: ms, + gridAutoColumns: ms, + gridAutoRows: ms, + gridAutoFlow: ms, + grid: ms, + gridRowStart: ms, + gridColumnStart: ms, + gridRowEnd: ms, + gridRow: ms, + gridColumn: ms, + gridColumnEnd: ms, + gridColumnGap: ms, + gridRowGap: ms, + gridArea: ms, + gridGap: ms, + textSizeAdjust: wms, + borderImage: w, + borderImageOutset: w, + borderImageRepeat: w, + borderImageSlice: w, + borderImageSource: w, + borderImageWidth: w, + transitionDelay: w, + transitionDuration: w, + transitionProperty: w, + transitionTimingFunction: w + } +}; diff --git a/src/modules/unitlessNumbers/index.js b/src/modules/unitlessNumbers/index.js index ae35bcd7..206f8750 100644 --- a/src/modules/unitlessNumbers/index.js +++ b/src/modules/unitlessNumbers/index.js @@ -46,4 +46,18 @@ const unitlessNumbers = { shadowOpacity: true }; +/** + * Support style names that may come passed in prefixed by adding permutations + * of vendor prefixes. + */ +const prefixes = ['ms', 'Moz', 'O', 'Webkit']; +const prefixKey = (prefix: string, key: string) => { + return prefix + key.charAt(0).toUpperCase() + key.substring(1); +}; +Object.keys(unitlessNumbers).forEach(prop => { + prefixes.forEach(prefix => { + unitlessNumbers[prefixKey(prefix, prop)] = unitlessNumbers[prop]; + }); +}); + export default unitlessNumbers; diff --git a/yarn.lock b/yarn.lock index 12d8e438..95b2cbdd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -894,6 +894,13 @@ browserify-zlib@^0.1.4: dependencies: pako "~0.2.0" +browserslist@^2.0.0: + version "2.2.2" + resolved "https://artifactory.twitter.biz:443/api/npm/js-virtual/browserslist/-/browserslist-2.2.2.tgz#e9b4618b8a01c193f9786beea09f6fd10dbe31c3" + dependencies: + caniuse-lite "^1.0.30000704" + electron-to-chromium "^1.3.16" + bser@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/bser/-/bser-1.0.2.tgz#381116970b2a6deea5646dd15dd7278444b56169" @@ -967,6 +974,19 @@ camelcase@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" +caniuse-api@^2.0.0: + version "2.0.0" + resolved "https://artifactory.twitter.biz:443/api/npm/js-virtual/caniuse-api/-/caniuse-api-2.0.0.tgz#b1ddb5a5966b16f48dc4998444d4bbc6c7d9d834" + dependencies: + browserslist "^2.0.0" + caniuse-lite "^1.0.0" + lodash.memoize "^4.1.2" + lodash.uniq "^4.5.0" + +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000704: + version "1.0.30000706" + resolved "https://artifactory.twitter.biz:443/api/npm/js-virtual/caniuse-lite/-/caniuse-lite-1.0.30000706.tgz#bc59abc41ba7d4a3634dda95befded6114e1f24e" + capture-stack-trace@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/capture-stack-trace/-/capture-stack-trace-1.0.0.tgz#4a6fa07399c26bba47f0b2496b4d0fb408c5550d" @@ -1553,6 +1573,10 @@ ejs@^2.5.6: version "2.5.6" resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.5.6.tgz#479636bfa3fe3b1debd52087f0acb204b4f19c88" +electron-to-chromium@^1.3.16: + version "1.3.16" + resolved "https://artifactory.twitter.biz:443/api/npm/js-virtual/electron-to-chromium/-/electron-to-chromium-1.3.16.tgz#d0e026735754770901ae301a21664cba45d92f7d" + elegant-spinner@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/elegant-spinner/-/elegant-spinner-1.0.1.tgz#db043521c95d7e303fd8f345bedc3349cfb0729e" @@ -3291,6 +3315,10 @@ lodash.map@^4.4.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.map/-/lodash.map-4.6.0.tgz#771ec7839e3473d9c4cde28b19394c3562f4f6d3" +lodash.memoize@^4.1.2: + version "4.1.2" + resolved "https://artifactory.twitter.biz:443/api/npm/js-virtual/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + lodash.merge@^4.4.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.0.tgz#69884ba144ac33fe699737a6086deffadd0f89c5" @@ -3319,6 +3347,10 @@ lodash.some@^4.4.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.some/-/lodash.some-4.6.0.tgz#1bb9f314ef6b8baded13b549169b2a945eb68e4d" +lodash.uniq@^4.5.0: + version "4.5.0" + resolved "https://artifactory.twitter.biz:443/api/npm/js-virtual/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" + lodash@^4.0.0, lodash@^4.14.0, lodash@^4.17.4, lodash@^4.3.0, lodash@^4.6.1: version "4.17.4" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"