├── .eslintrc.js ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .size-snapshot.json ├── .travis.yml ├── LICENSE ├── babel.config.js ├── benchmark └── tests │ ├── supported-property.js │ └── supported-value.js ├── browsers.json ├── changelog.md ├── karma.conf.js ├── package.json ├── readme.md ├── rollup.config.js ├── src ├── index.js ├── index.test.js ├── plugins │ ├── appearence.js │ ├── break-props-old.js │ ├── color-adjust.js │ ├── flex-2009.js │ ├── flex-2012.js │ ├── index.js │ ├── inline-logical-old.js │ ├── mask.js │ ├── overscroll-behavior.js │ ├── prefixed.js │ ├── scroll-snap.js │ ├── text-orientation.js │ ├── transform.js │ ├── transition.js │ ├── unprefixed.js │ ├── user-select.js │ └── writing-mode.js ├── prefix.js ├── prefix.test.js ├── supported-keyframes.js ├── supported-keyframes.test.js ├── supported-property.js ├── supported-property.test.js ├── supported-value.js ├── supported-value.test.js └── utils │ ├── camelize.js │ └── pascalize.js ├── tests.html ├── tests.webpack.js ├── tests └── fixtures.js ├── webpack.config.js └── yarn.lock /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: 'babel-eslint', 3 | extends: ['jss', 'prettier'], 4 | globals: { 5 | benchmark: true 6 | }, 7 | env: { 8 | mocha: true, 9 | browser: true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | npm-debug.log 4 | local.log 5 | *~ 6 | lib 7 | dist 8 | coverage 9 | tmp 10 | .env 11 | libpeerconnection.log 12 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | lib/ 4 | *.html 5 | *.md 6 | # npm install does it's own formatting, which can conflict with prettier 7 | package.json 8 | !packages/**/package.json 9 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "bracketSpacing": false, 6 | "printWidth": 100 7 | } 8 | -------------------------------------------------------------------------------- /.size-snapshot.json: -------------------------------------------------------------------------------- 1 | { 2 | "dist/css-vendor.js": { 3 | "bundled": 17816, 4 | "minified": 6103, 5 | "gzipped": 2361 6 | }, 7 | "dist/css-vendor.min.js": { 8 | "bundled": 17816, 9 | "minified": 6103, 10 | "gzipped": 2361 11 | }, 12 | "./lib/index": { 13 | "bundled": 14406, 14 | "minified": 6274, 15 | "gzipped": 2186 16 | }, 17 | "./dist/css-vendor.cjs.js": { 18 | "bundled": 14591, 19 | "minified": 6157, 20 | "gzipped": 2100 21 | }, 22 | "./dist/css-vendor.esm.js": { 23 | "bundled": 14272, 24 | "minified": 5896, 25 | "gzipped": 2011, 26 | "treeshaked": { 27 | "rollup": { 28 | "code": 3287, 29 | "import_statements": 89 30 | }, 31 | "webpack": { 32 | "code": 4481 33 | } 34 | } 35 | }, 36 | "dist/css-vendor.cjs.js": { 37 | "bundled": 15239, 38 | "minified": 6581, 39 | "gzipped": 2233 40 | }, 41 | "dist/css-vendor.esm.js": { 42 | "bundled": 14920, 43 | "minified": 6320, 44 | "gzipped": 2145, 45 | "treeshaked": { 46 | "rollup": { 47 | "code": 3613, 48 | "import_statements": 89 49 | }, 50 | "webpack": { 51 | "code": 4817 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '10' 4 | branches: 5 | only: 6 | - master 7 | - /^greenkeeper/.*$/ 8 | dd: 9 | secure: OhXW1SOYeMBB76/EN5AGzz5DvSAd30XGjT7fwueBUk3t05Y66klg92SkprpOxKuoAjW17EC8+gBAi6+WLHF/GoNNKulvhmLe+J30OwB0gCzhedB269Wlq2JHUseKVYQEfJ5mah7v5V4UYLDqBTAOdRlWslIF56eOK9LES/k6CTA= 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2014-present Oleg Slobodskoi 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = api => { 2 | api.cache(true) 3 | const presets = [['@babel/env', {loose: true}]] 4 | const plugins = [ 5 | 'transform-es2015-spread', 6 | 'transform-es3-member-expression-literals', 7 | 'transform-es3-property-literals' 8 | ] 9 | 10 | if (process.env.NODE_ENV === 'test') { 11 | plugins.push('rewire') 12 | } 13 | 14 | return { 15 | presets, 16 | plugins 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /benchmark/tests/supported-property.js: -------------------------------------------------------------------------------- 1 | import supportedProperty from '../../src/supported-property' 2 | 3 | suite('Supported property', () => { 4 | benchmark('margin', () => supportedProperty('margin') === 'margin') 5 | 6 | benchmark('transition', () => supportedProperty('transition') === 'transition') 7 | 8 | benchmark('appearance', () => supportedProperty('appearance') === 'appearance') 9 | 10 | benchmark('flex-positive', () => supportedProperty('flex-positive') === 'flex-grow') 11 | }) 12 | -------------------------------------------------------------------------------- /benchmark/tests/supported-value.js: -------------------------------------------------------------------------------- 1 | import {supportedValue} from '../../src' 2 | 3 | suite('Supported value', () => { 4 | benchmark('background-color', () => supportedValue('background-color', 'white') === 'white') 5 | 6 | benchmark( 7 | 'transform with double array value', 8 | () => 9 | supportedValue('transition', 'all 100ms ease, transform 200ms linear') === 10 | 'all 100ms ease, transform 200ms linear' 11 | ) 12 | }) 13 | -------------------------------------------------------------------------------- /browsers.json: -------------------------------------------------------------------------------- 1 | { 2 | "BS_MobileSafari": { 3 | "base": "BrowserStack", 4 | "os": "ios", 5 | "os_version": "11.0", 6 | "browser": "iphone", 7 | "real_mobile": false 8 | }, 9 | "BS_Opera52": { 10 | "base": "BrowserStack", 11 | "os": "OS X", 12 | "os_version": "Mojave", 13 | "browser": "opera" 14 | }, 15 | "BS_Chrome": { 16 | "base": "BrowserStack", 17 | "os": "OS X", 18 | "os_version": "Mojave", 19 | "browser": "chrome" 20 | }, 21 | "BS_Safari": { 22 | "base": "BrowserStack", 23 | "os": "OS X", 24 | "os_version": "Mojave", 25 | "browser": "safari" 26 | }, 27 | "BS_Firefox": { 28 | "base": "BrowserStack", 29 | "os": "Windows", 30 | "os_version": "10", 31 | "browser": "firefox" 32 | }, 33 | "BS_InternetExplorer9": { 34 | "base": "BrowserStack", 35 | "os": "Windows", 36 | "os_version": "7", 37 | "browser": "ie" 38 | }, 39 | "BS_InternetExplorer10": { 40 | "base": "BrowserStack", 41 | "os": "Windows", 42 | "os_version": "8", 43 | "browser": "ie" 44 | }, 45 | "BS_InternetExplorer11": { 46 | "base": "BrowserStack", 47 | "os": "Windows", 48 | "os_version": "8.1", 49 | "browser": "ie" 50 | }, 51 | "BS_Edge13": { 52 | "base": "BrowserStack", 53 | "os": "Windows", 54 | "os_version": "10", 55 | "browser": "edge" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | ## 2.0.7 / 2019-10-04 2 | 3 | - Return parameters for transition/transition-property without prefix if they don't need to be prefixed 4 | - Update dependencies 5 | 6 | ## 2.0.6 / 2019-08-15 7 | 8 | - Freed from prefixing values when content property used 9 | - Update dependencies 10 | 11 | ## 2.0.5 / 2019-06-20 12 | 13 | - Freed from prefixing values with custom CSS variables for transition/transition-property properties 14 | 15 | ## 2.0.4 / 2019-06-15 16 | 17 | - Fixed server compatibility 18 | 19 | ## 2.0.3 / 2019-06-15 20 | 21 | - Added support for text-orientation property 22 | - Removed support for text-decoration-skip-ink property 23 | - Exempt properties which starts from '-' (already prefixed properties) or '--' (custom CSS variables) from prefixing 24 | - Update dependencies 25 | 26 | ## 2.0.2 / 2019-04-08 27 | 28 | - Fix prefix for backdrop-filter on Edge 17/18 29 | 30 | ## 2.0.1 / 2019-03-21 31 | 32 | - Fix prefixing values 33 | - Update dependencies 34 | 35 | ## 2.0.0 / 2019-02-11 36 | 37 | - Remove default exports 38 | 39 | ## 1.2.1 / 2019-02-11 40 | 41 | - Fix prefixing flex value for IE 10 42 | - Workaround esm with default export 43 | 44 | ## 1.2.0 / 2019-02-10 45 | 46 | - Support for inline props syntax. e.g. border-inline-end, margin-inline-start 47 | - Support place-self 48 | - Support text-decoration-skip-ink 49 | - Bundle with rollup 50 | - Add prettier 51 | 52 | ## 1.1.0 / 2018-07-08 53 | 54 | - Added supportedKeyframes function, to prefix @keyframes at-rule 55 | 56 | ## 1.0.4 / 2018-07-04 57 | 58 | - Fix for dashed property values 59 | 60 | ## 1.0.3 / 2018-02-15 61 | 62 | - Fixed functions in values 63 | 64 | ## 1.0.2 / 2018-01-22 65 | 66 | - Catch errors when testing a property 67 | 68 | ## 1.0.1 / 2018-01-15 69 | 70 | - Fix Number.isNaN 71 | 72 | ## 1.0.0 / 2018-01-02 73 | 74 | - Added support for various properties for old browsers, undetectable with feature tests: 75 | - appearance 76 | - break-* 77 | - clip-path 78 | - filter 79 | - flex 80 | - (border|margin|padding)-inline 81 | - mask-* 82 | - scroll-snap 83 | - transform 84 | - transition 85 | - writing-mode 86 | - Use autoprefixer data to generate tests for full compatibility. 87 | 88 | ## 0.3.8 / 2016-11-17 89 | 90 | - better cache prefill 91 | - fix supportedValue 92 | - migrate tests 93 | - introduce browserstack config and travis 94 | 95 | ## 0.3.7 / 2016-10-31 96 | 97 | - use cross-env 98 | - use is-in-browser 99 | 100 | ## 0.3.6 / 2016-10-17 101 | 102 | - cheeck webkit prefix as last, because other browsers use it as well 103 | - update deps 104 | 105 | ## 0.3.5 / 2016-08-23 106 | 107 | - better browser env detection 108 | - update deps 109 | 110 | ## 0.3.4 / 2016-06-15 111 | 112 | - catch IE exceptions when feature testing a value 113 | 114 | ## 0.3.3 / 2015-04-13 115 | 116 | - do nothing server-side 117 | 118 | ## 0.3.2 / 2015-03-24 119 | 120 | - update babel, lint 121 | 122 | ## 0.3.1 / 2015-10-21 123 | 124 | - release package with lib dir 125 | 126 | ## 0.3.0 / 2015-10-21 127 | 128 | - migrate to ES6 with babel 129 | - remove bower.json 130 | - simplify packaging 131 | 132 | ## 0.2.5 / 2014-22-09 133 | 134 | - make it requirable serverside 135 | 136 | ## 0.2.4 / 2014-21-09 137 | 138 | - make it requirable serverside 139 | 140 | ## 0.2.3 / 2014-02-08 141 | 142 | - don't make feature tests for values like '10px' 143 | 144 | ## 0.2.2 / 2014-02-08 145 | 146 | - slightly change the api - now always return the corrected value, even if prefix is not needed 147 | 148 | ## 0.2.0 / 2014-02-07 149 | 150 | - add supported value feature test 151 | 152 | ## 0.1.0 / 2014-11-29 153 | 154 | - first release in a separate repository for plugins 155 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | const webpackConfig = require('./webpack.config') 2 | const browsers = require('./browsers') 3 | 4 | const useCloud = process.env.USE_CLOUD === 'true' 5 | const browserStackUserName = process.env.BROWSER_STACK_USERNAME 6 | const browserStackAccessKey = process.env.BROWSER_STACK_ACCESS_KEY 7 | const isTravis = process.env.TRAVIS 8 | const travisBuildNumber = process.env.TRAVIS_BUILD_NUMBER 9 | const travisBuildId = process.env.TRAVIS_BUILD_ID 10 | const travisJobNumber = process.env.TRAVIS_JOB_NUMBER 11 | const isBench = process.env.NODE_ENV === 'benchmark' 12 | 13 | module.exports = config => { 14 | config.set({ 15 | customLaunchers: browsers, 16 | browsers: ['Chrome'], 17 | frameworks: ['mocha'], 18 | files: [ 19 | 'node_modules/es5-shim/es5-shim.js', 20 | 'node_modules/es5-shim/es5-sham.js', 21 | 'tests.webpack.js' 22 | ], 23 | preprocessors: { 24 | 'tests.webpack.js': ['webpack', 'sourcemap'] 25 | }, 26 | webpack: Object.assign(webpackConfig, { 27 | devtool: 'inline-source-map', 28 | module: { 29 | rules: [ 30 | Object.assign(webpackConfig.module.rules[0], { 31 | exclude: /node_modules(?!(\/|\\)camelcase-css)/ 32 | }) 33 | ] 34 | } 35 | }), 36 | webpackServer: { 37 | noInfo: true 38 | }, 39 | reporters: ['mocha'] 40 | }) 41 | 42 | if (isBench) { 43 | Object.assign(config, { 44 | browsers: ['Chrome'], 45 | frameworks: ['benchmark'], 46 | // Using a fixed position for a file name, m.b. should use an args parser later. 47 | files: [process.argv[4] || 'benchmark/**/*.js'], 48 | preprocessors: {'benchmark/**/*.js': ['webpack']}, 49 | reporters: ['benchmark'], 50 | // Some tests are slow. 51 | browserNoActivityTimeout: 20000 52 | }) 53 | } 54 | 55 | if (useCloud) { 56 | Object.assign(config, { 57 | browsers: Object.keys(browsers), 58 | // My current OS plan allows max 2 parallel connections. 59 | concurrency: 2, 60 | retryLimit: 3, 61 | 62 | // Timeouts taken from https://github.com/karma-runner/karma-browserstack-launcher/issues/61 63 | captureTimeout: 3e5, 64 | browserNoActivityTimeout: 3e5, 65 | browserDisconnectTimeout: 3e5, 66 | browserDisconnectTolerance: 3 67 | }) 68 | 69 | config.browserStack = { 70 | username: browserStackUserName, 71 | accessKey: browserStackAccessKey, 72 | captureTimeout: 3e5 73 | } 74 | 75 | if (isTravis) { 76 | Object.assign(config.browserStack, { 77 | build: `TRAVIS #${travisBuildNumber} (${travisBuildId})`, 78 | name: travisJobNumber 79 | }) 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "css-vendor", 3 | "description": "CSS vendor prefix detection and property feature testing.", 4 | "version": "2.0.8", 5 | "author": { 6 | "name": "Oleg Slobodskoi", 7 | "email": "oleg008@gmail.com" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git@github.com:cssinjs/css-vendor.git" 12 | }, 13 | "keywords": [ 14 | "css", 15 | "vendor", 16 | "feature", 17 | "test", 18 | "prefix", 19 | "cssinjs", 20 | "jss", 21 | "css-in-js" 22 | ], 23 | "engines": {}, 24 | "main": "./dist/css-vendor.cjs.js", 25 | "module": "./dist/css-vendor.esm.js", 26 | "files": [ 27 | "dist" 28 | ], 29 | "scripts": { 30 | "all": "yarn lint && yarn test && yarn build", 31 | "build": "yarn clean && yarn rollup -c", 32 | "clean": "rimraf {dist,tmp}/*", 33 | "lint": "eslint ./src ./tests --fix", 34 | "lint:staged": "lint-staged", 35 | "format": "prettier \"*.js\" \"{tests,src,benchmark}/**/*.js\" --write", 36 | "test": "cross-env NODE_ENV=test karma start --single-run ", 37 | "test:watch": "cross-env NODE_ENV=test karma start", 38 | "bench": "cross-env NODE_ENV=benchmark karma start --single-run", 39 | "prepublishOnly": "yarn all" 40 | }, 41 | "license": "MIT", 42 | "devDependencies": { 43 | "@babel/core": "^7.10.2", 44 | "@babel/plugin-transform-runtime": "^7.10.1", 45 | "@babel/preset-env": "^7.10.2", 46 | "autoprefixer": "^9.7.6", 47 | "babel-cli": "^6.26.0", 48 | "babel-eslint": "^10.1.0", 49 | "babel-loader": "^8.1.0", 50 | "babel-plugin-rewire": "^1.2.0", 51 | "babel-plugin-transform-es2015-spread": "^6.22.0", 52 | "babel-plugin-transform-es3-member-expression-literals": "^6.22.0", 53 | "babel-plugin-transform-es3-property-literals": "^6.22.0", 54 | "caniuse-support": "^1.0.4", 55 | "cross-env": "^7.0.2", 56 | "es5-shim": "^4.5.14", 57 | "eslint": "^7.0.0", 58 | "eslint-config-jss": "^5.0.1", 59 | "eslint-config-prettier": "^6.11.0", 60 | "expect.js": "^0.3.1", 61 | "karma": "^5.0.5", 62 | "karma-benchmark": "^1.0.4", 63 | "karma-benchmark-reporter": "^0.1.1", 64 | "karma-browserstack-launcher": "^1.5.2", 65 | "karma-chrome-launcher": "^3.1.0", 66 | "karma-firefox-launcher": "^1.3.0", 67 | "karma-mocha": "^2.0.1", 68 | "karma-mocha-reporter": "^2.2.5", 69 | "karma-opera-launcher": "^1.0.0", 70 | "karma-safari-launcher": "^1.0.0", 71 | "karma-sourcemap-loader": "^0.3.7", 72 | "karma-webpack": "^4.0.2", 73 | "lint-staged": "^10.2.6", 74 | "mocha": "^7.1.2", 75 | "postcss-js": "^2.0.3", 76 | "pre-commit": "^1.2.2", 77 | "prettier": "^2.0.5", 78 | "rimraf": "^3.0.2", 79 | "rollup": "^2.10.2", 80 | "rollup-plugin-babel": "^4.4.0", 81 | "rollup-plugin-node-resolve": "^5.2.0", 82 | "rollup-plugin-replace": "^2.2.0", 83 | "rollup-plugin-size-snapshot": "^0.12.0", 84 | "rollup-plugin-terser": "^6.0.1", 85 | "webpack": "^4.43.0", 86 | "webpack-cli": "^3.3.11" 87 | }, 88 | "dependencies": { 89 | "@babel/runtime": "^7.12.13", 90 | "is-in-browser": "^1.1.3" 91 | }, 92 | "lint-staged": { 93 | "**/*.js": [ 94 | "eslint --fix", 95 | "prettier --write", 96 | "git add" 97 | ] 98 | }, 99 | "pre-commit": "lint:staged" 100 | } 101 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | [](https://travis-ci.org/cssinjs/css-vendor) [](https://greenkeeper.io/) 2 | 3 | ## CSS vendor prefix detection and property feature testing. 4 | 5 | ### Vendor prefixes 6 | 7 | ```javascript 8 | console.log(cssVendor.prefix.js) // e.g. WebkitTransform 9 | 10 | console.log(cssVendor.prefix.css) // e.g. -webkit-transform 11 | ``` 12 | 13 | ### Property support feature test 14 | 15 | `cssVendor.supportedProperty(prop)` 16 | 17 | Test if property is supported, returns false if not. Returns string if supported. May add a vendor prefix if needed. 18 | 19 | ```javascript 20 | console.log(cssVendor.supportedProperty('animation')) // e.g. -webkit-animation 21 | ``` 22 | 23 | ### Value support feature test 24 | 25 | `cssVendor.supportedValue(prop, value)` 26 | 27 | Test if value is supported, returns false if not. Returns string if supported. May add a vendor prefix if needed. 28 | 29 | ```javascript 30 | console.log(cssVendor.supportedValue('display', 'flex')) // e.g. -webkit-flex 31 | ``` 32 | 33 | ## Run tests 34 | 35 | ```bash 36 | yarn 37 | yarn test 38 | ``` 39 | 40 | ## License 41 | 42 | MIT 43 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import nodeResolve from 'rollup-plugin-node-resolve' 2 | import babel from 'rollup-plugin-babel' 3 | import replace from 'rollup-plugin-replace' 4 | import {terser} from 'rollup-plugin-terser' 5 | import {sizeSnapshot} from 'rollup-plugin-size-snapshot' 6 | import pkg from './package.json' 7 | 8 | const input = './src/index.js' 9 | 10 | const name = 'cssVendor' 11 | 12 | const external = id => !id.startsWith('.') && !id.startsWith('/') 13 | 14 | const getBabelOptions = ({useESModules}) => ({ 15 | exclude: /node_modules/, 16 | runtimeHelpers: true, 17 | plugins: [['@babel/transform-runtime', {useESModules}]] 18 | }) 19 | 20 | export default [ 21 | { 22 | input, 23 | output: {file: `dist/${pkg.name}.js`, format: 'umd', name}, 24 | plugins: [ 25 | nodeResolve(), 26 | babel(getBabelOptions({useESModules: true})), 27 | replace({'process.env.NODE_ENV': JSON.stringify('development')}), 28 | sizeSnapshot() 29 | ] 30 | }, 31 | 32 | { 33 | input, 34 | output: {file: `dist/${pkg.name}.min.js`, format: 'umd', name}, 35 | plugins: [ 36 | nodeResolve(), 37 | babel(getBabelOptions({useESModules: true})), 38 | replace({'process.env.NODE_ENV': JSON.stringify('production')}), 39 | sizeSnapshot(), 40 | terser() 41 | ] 42 | }, 43 | 44 | { 45 | input, 46 | output: {file: pkg.main, format: 'cjs'}, 47 | external, 48 | plugins: [babel(getBabelOptions({useESModules: false})), sizeSnapshot()] 49 | }, 50 | 51 | { 52 | input, 53 | output: {file: pkg.module, format: 'esm'}, 54 | external, 55 | plugins: [babel(getBabelOptions({useESModules: true})), sizeSnapshot()] 56 | } 57 | ] 58 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * CSS Vendor prefix detection and property feature testing. 3 | * 4 | * @copyright Oleg Slobodskoi 2015 5 | * @website https://github.com/jsstyles/css-vendor 6 | * @license MIT 7 | */ 8 | 9 | import prefix from './prefix' 10 | import supportedKeyframes from './supported-keyframes' 11 | import supportedProperty from './supported-property' 12 | import supportedValue from './supported-value' 13 | 14 | export {prefix, supportedKeyframes, supportedProperty, supportedValue} 15 | -------------------------------------------------------------------------------- /src/index.test.js: -------------------------------------------------------------------------------- 1 | import {detectBrowser} from 'caniuse-support' 2 | 3 | const currentBrowser = detectBrowser(window.navigator.userAgent) 4 | 5 | const msg = `Detected browser: ${currentBrowser.id} ${currentBrowser.version}` 6 | console.log(msg) // eslint-disable-line no-console 7 | -------------------------------------------------------------------------------- /src/plugins/appearence.js: -------------------------------------------------------------------------------- 1 | import prefix from '../prefix' 2 | 3 | // Support old appearance props syntax. 4 | // https://caniuse.com/#search=appearance 5 | export default { 6 | noPrefill: ['appearance'], 7 | supportedProperty: prop => { 8 | if (prop !== 'appearance') return false 9 | if (prefix.js === 'ms') return `-webkit-${prop}` 10 | return prefix.css + prop 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/plugins/break-props-old.js: -------------------------------------------------------------------------------- 1 | import prefix from '../prefix' 2 | import pascalize from '../utils/pascalize' 3 | 4 | // Support old break-* props syntax. 5 | // https://caniuse.com/#search=multicolumn 6 | // https://github.com/postcss/autoprefixer/issues/491 7 | // https://github.com/postcss/autoprefixer/issues/177 8 | export default { 9 | supportedProperty: (prop, style) => { 10 | if (!/^break-/.test(prop)) return false 11 | if (prefix.js === 'Webkit') { 12 | const jsProp = `WebkitColumn${pascalize(prop)}` 13 | return jsProp in style ? `${prefix.css}column-${prop}` : false 14 | } 15 | if (prefix.js === 'Moz') { 16 | const jsProp = `page${pascalize(prop)}` 17 | return jsProp in style ? `page-${prop}` : false 18 | } 19 | return false 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/plugins/color-adjust.js: -------------------------------------------------------------------------------- 1 | import prefix from '../prefix' 2 | 3 | // Support old color-adjust prop syntax. 4 | // https://caniuse.com/#search=color-adjust 5 | export default { 6 | noPrefill: ['color-adjust'], 7 | supportedProperty: prop => { 8 | if (prop !== 'color-adjust') return false 9 | if (prefix.js === 'Webkit') return `${prefix.css}print-${prop}` 10 | return prop 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/plugins/flex-2009.js: -------------------------------------------------------------------------------- 1 | import prefix from '../prefix' 2 | import pascalize from '../utils/pascalize' 3 | 4 | const propMap = { 5 | flex: 'box-flex', 6 | 'flex-grow': 'box-flex', 7 | 'flex-direction': ['box-orient', 'box-direction'], 8 | order: 'box-ordinal-group', 9 | 'align-items': 'box-align', 10 | 'flex-flow': ['box-orient', 'box-direction'], 11 | 'justify-content': 'box-pack' 12 | } 13 | 14 | const propKeys = Object.keys(propMap) 15 | 16 | const prefixCss = p => prefix.css + p 17 | 18 | // Support old flex spec from 2009. 19 | export default { 20 | supportedProperty: (prop, style, {multiple}) => { 21 | if (propKeys.indexOf(prop) > -1) { 22 | const newProp = propMap[prop] 23 | if (!Array.isArray(newProp)) { 24 | return prefix.js + pascalize(newProp) in style ? prefix.css + newProp : false 25 | } 26 | if (!multiple) return false 27 | for (let i = 0; i < newProp.length; i++) { 28 | if (!(prefix.js + pascalize(newProp[0]) in style)) { 29 | return false 30 | } 31 | } 32 | return newProp.map(prefixCss) 33 | } 34 | return false 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/plugins/flex-2012.js: -------------------------------------------------------------------------------- 1 | import prefix from '../prefix' 2 | import pascalize from '../utils/pascalize' 3 | 4 | const propMap = { 5 | 'flex-grow': 'flex-positive', 6 | 'flex-shrink': 'flex-negative', 7 | 'flex-basis': 'flex-preferred-size', 8 | 'justify-content': 'flex-pack', 9 | order: 'flex-order', 10 | 'align-items': 'flex-align', 11 | 'align-content': 'flex-line-pack' 12 | // 'align-self' is handled by 'align-self' plugin. 13 | } 14 | 15 | // Support old flex spec from 2012. 16 | export default { 17 | supportedProperty: (prop, style) => { 18 | const newProp = propMap[prop] 19 | if (!newProp) return false 20 | return prefix.js + pascalize(newProp) in style ? prefix.css + newProp : false 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/plugins/index.js: -------------------------------------------------------------------------------- 1 | import appearence from './appearence' 2 | import colorAdjust from './color-adjust' 3 | import mask from './mask' 4 | import textOrientation from './text-orientation' 5 | import transform from './transform' 6 | import transition from './transition' 7 | import writingMode from './writing-mode' 8 | import userSelect from './user-select' 9 | import breakPropsOld from './break-props-old' 10 | import inlineLogicalOld from './inline-logical-old' 11 | import unprefixed from './unprefixed' 12 | import prefixed from './prefixed' 13 | import scrollSnap from './scroll-snap' 14 | import overscrollBehavior from './overscroll-behavior' 15 | import flex2012 from './flex-2012' 16 | import flex2009 from './flex-2009' 17 | 18 | // Please, keep order plugins: 19 | // plugins = [ 20 | // ...plugins, 21 | // breakPropsOld, 22 | // inlineLogicalOld, 23 | // unprefixed, 24 | // prefixed, 25 | // scrollSnap, 26 | // flex2012, 27 | // flex2009 28 | // ] 29 | // Plugins without 'noPrefill' value, going last. 30 | // 'flex-*' plugins should be at the bottom. 31 | // 'flex2009' going after 'flex2012'. 32 | // 'prefixed' going after 'unprefixed' 33 | const plugins = [ 34 | appearence, 35 | colorAdjust, 36 | mask, 37 | textOrientation, 38 | transform, 39 | transition, 40 | writingMode, 41 | userSelect, 42 | breakPropsOld, 43 | inlineLogicalOld, 44 | unprefixed, 45 | prefixed, 46 | scrollSnap, 47 | overscrollBehavior, 48 | flex2012, 49 | flex2009 50 | ] 51 | 52 | export const propertyDetectors = plugins 53 | .filter(p => p.supportedProperty) 54 | .map(p => p.supportedProperty) 55 | 56 | export const noPrefill = plugins 57 | .filter(p => p.noPrefill) 58 | .reduce((a, p) => { 59 | a.push(...p.noPrefill) 60 | return a 61 | }, []) 62 | -------------------------------------------------------------------------------- /src/plugins/inline-logical-old.js: -------------------------------------------------------------------------------- 1 | import prefix from '../prefix' 2 | import pascalize from '../utils/pascalize' 3 | 4 | // Support old inline-logical syntax. 5 | // See https://github.com/postcss/autoprefixer/issues/324. 6 | export default { 7 | supportedProperty: (prop, style) => { 8 | if (!/^(border|margin|padding)-inline/.test(prop)) return false 9 | if (prefix.js === 'Moz') return prop 10 | const newProp = prop.replace('-inline', '') 11 | return prefix.js + pascalize(newProp) in style ? prefix.css + newProp : false 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/plugins/mask.js: -------------------------------------------------------------------------------- 1 | import prefix from '../prefix' 2 | import pascalize from '../utils/pascalize' 3 | import camelize from '../utils/camelize' 4 | 5 | // Mask property support cannot detect directly in WebKit browsers, 6 | // but we can use a longhand property instead. 7 | // https://caniuse.com/#search=mask 8 | export default { 9 | noPrefill: ['mask'], 10 | supportedProperty: (prop, style) => { 11 | if (!/^mask/.test(prop)) return false 12 | if (prefix.js === 'Webkit') { 13 | const longhand = 'mask-image' 14 | if (camelize(longhand) in style) { 15 | return prop 16 | } 17 | if (prefix.js + pascalize(longhand) in style) { 18 | return prefix.css + prop 19 | } 20 | } 21 | return prop 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/plugins/overscroll-behavior.js: -------------------------------------------------------------------------------- 1 | import prefix from '../prefix' 2 | 3 | // Support overscroll-behavior props syntax. 4 | // https://caniuse.com/#search=overscroll-behavior 5 | export default { 6 | supportedProperty: prop => { 7 | if (prop !== 'overscroll-behavior') return false 8 | if (prefix.js === 'ms') { 9 | return `${prefix.css}scroll-chaining` 10 | } 11 | return prop 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/plugins/prefixed.js: -------------------------------------------------------------------------------- 1 | import prefix from '../prefix' 2 | import pascalize from '../utils/pascalize' 3 | 4 | // Test if property is supported with vendor prefix. 5 | export default { 6 | supportedProperty: (prop, style) => { 7 | const pascalized = pascalize(prop) 8 | // Return custom CSS variable without prefixing. 9 | if (prop[0] === '-') return prop 10 | // Return already prefixed value without prefixing. 11 | if (prop[0] === '-' && prop[1] === '-') return prop 12 | if (prefix.js + pascalized in style) return prefix.css + prop 13 | // Try webkit fallback. 14 | if (prefix.js !== 'Webkit' && `Webkit${pascalized}` in style) return `-webkit-${prop}` 15 | return false 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/plugins/scroll-snap.js: -------------------------------------------------------------------------------- 1 | import prefix from '../prefix' 2 | 3 | // Support scroll-snap props syntax. 4 | // https://caniuse.com/#search=scroll-snap 5 | export default { 6 | supportedProperty: prop => { 7 | if (prop.substring(0, 11) !== 'scroll-snap') return false 8 | if (prefix.js === 'ms') { 9 | return `${prefix.css}${prop}` 10 | } 11 | return prop 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/plugins/text-orientation.js: -------------------------------------------------------------------------------- 1 | import prefix from '../prefix' 2 | 3 | // Support text-orientation prop syntax. 4 | // https://caniuse.com/#search=text-orientation 5 | export default { 6 | noPrefill: ['text-orientation'], 7 | supportedProperty: prop => { 8 | if (prop !== 'text-orientation') return false 9 | if (prefix.vendor === 'apple' && !prefix.isTouch) { 10 | return prefix.css + prop 11 | } 12 | return prop 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/plugins/transform.js: -------------------------------------------------------------------------------- 1 | import prefix from '../prefix' 2 | 3 | // Support transform prop syntax. 4 | // https://caniuse.com/#search=transform 5 | export default { 6 | noPrefill: ['transform'], 7 | supportedProperty: (prop, style, options) => { 8 | if (prop !== 'transform') return false 9 | if (options.transform) { 10 | return prop 11 | } 12 | return prefix.css + prop 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/plugins/transition.js: -------------------------------------------------------------------------------- 1 | import prefix from '../prefix' 2 | 3 | // Support transition prop syntax. 4 | // https://caniuse.com/#search=transition 5 | export default { 6 | noPrefill: ['transition'], 7 | supportedProperty: (prop, style, options) => { 8 | if (prop !== 'transition') return false 9 | if (options.transition) { 10 | return prop 11 | } 12 | return prefix.css + prop 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/plugins/unprefixed.js: -------------------------------------------------------------------------------- 1 | import camelize from '../utils/camelize' 2 | 3 | // Test if a property supported as it is. 4 | // Camelization is required because we can't test using. 5 | // CSS syntax for e.g. in FF. 6 | export default { 7 | supportedProperty: (prop, style) => (camelize(prop) in style ? prop : false) 8 | } 9 | -------------------------------------------------------------------------------- /src/plugins/user-select.js: -------------------------------------------------------------------------------- 1 | import prefix from '../prefix' 2 | 3 | // Support text-orientation prop syntax. 4 | // https://caniuse.com/#search=user-select 5 | export default { 6 | noPrefill: ['user-select'], 7 | supportedProperty: prop => { 8 | if (prop !== 'user-select') return false 9 | if (prefix.js === 'Moz' || prefix.js === 'ms' || prefix.vendor === 'apple') { 10 | return prefix.css + prop 11 | } 12 | return prop 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/plugins/writing-mode.js: -------------------------------------------------------------------------------- 1 | import prefix from '../prefix' 2 | 3 | // Support writing-mode prop syntax. 4 | // https://caniuse.com/#search=writing-mode 5 | export default { 6 | noPrefill: ['writing-mode'], 7 | supportedProperty: prop => { 8 | if (prop !== 'writing-mode') return false 9 | if (prefix.js === 'Webkit' || (prefix.js === 'ms' && prefix.browser !== 'edge')) { 10 | return prefix.css + prop 11 | } 12 | return prop 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/prefix.js: -------------------------------------------------------------------------------- 1 | // Export javascript style and css style vendor prefixes. 2 | // Based on "transform" support test. 3 | 4 | import isInBrowser from 'is-in-browser' 5 | 6 | let js = '' 7 | let css = '' 8 | let vendor = '' 9 | let browser = '' 10 | const isTouch = isInBrowser && document.documentElement && 'ontouchstart' in document.documentElement 11 | 12 | // We should not do anything if required serverside. 13 | if (isInBrowser) { 14 | // Order matters. We need to check Webkit the last one because 15 | // other vendors use to add Webkit prefixes to some properties 16 | const jsCssMap = { 17 | Moz: '-moz-', 18 | ms: '-ms-', 19 | O: '-o-', 20 | Webkit: '-webkit-' 21 | } 22 | 23 | const {style} = document.createElement('p') 24 | const testProp = 'Transform' 25 | 26 | for (const key in jsCssMap) { 27 | if (key + testProp in style) { 28 | js = key 29 | css = jsCssMap[key] 30 | break 31 | } 32 | } 33 | 34 | // Correctly detect the Edge browser. 35 | if (js === 'Webkit' && 'msHyphens' in style) { 36 | js = 'ms' 37 | css = jsCssMap.ms 38 | browser = 'edge' 39 | } 40 | 41 | // Correctly detect the Safari browser. 42 | if ( 43 | js === 'Webkit' && 44 | ('-apple-trailing-word' in style || 45 | (typeof CSS !== 'undefined' && CSS.supports('-webkit-backdrop-filter', 'blur(1px)'))) 46 | ) { 47 | vendor = 'apple' 48 | } 49 | } 50 | 51 | /** 52 | * Vendor prefix string for the current browser. 53 | * 54 | * @type {{js: String, css: String, vendor: String, browser: String}} 55 | * @api public 56 | */ 57 | export default {js, css, vendor, browser, isTouch} 58 | -------------------------------------------------------------------------------- /src/prefix.test.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect.js' 2 | import prefix from './prefix' 3 | 4 | describe('css-vendor', () => { 5 | describe('.prefix', () => { 6 | it('should be correct for .css', () => { 7 | const {css} = prefix 8 | expect(css).to.be.a('string') 9 | expect(css[0]).to.be('-') 10 | expect(css[css.length - 1]).to.be('-') 11 | expect(css.length >= 3).to.be(true) 12 | }) 13 | 14 | it('shoud be not empty for .js', () => { 15 | expect(prefix.js).to.be.a('string') 16 | }) 17 | }) 18 | }) 19 | -------------------------------------------------------------------------------- /src/supported-keyframes.js: -------------------------------------------------------------------------------- 1 | import prefix from './prefix' 2 | 3 | /** 4 | * Test if a keyframe at-rule should be prefixed or not 5 | * 6 | * @param {String} vendor prefix string for the current browser. 7 | * @return {String} 8 | * @api public 9 | */ 10 | 11 | export default function supportedKeyframes(key) { 12 | // Keyframes is already prefixed. e.g. key = '@-webkit-keyframes a' 13 | if (key[1] === '-') return key 14 | // No need to prefix IE/Edge. Older browsers will ignore unsupported rules. 15 | // https://caniuse.com/#search=keyframes 16 | if (prefix.js === 'ms') return key 17 | return `@${prefix.css}keyframes${key.substr(10)}` 18 | } 19 | -------------------------------------------------------------------------------- /src/supported-keyframes.test.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect.js' 2 | import supportedKeyframes from './supported-keyframes' 3 | 4 | describe('css-vendor', () => { 5 | describe('.supportedKeyframes()', () => { 6 | it('should not prefix keyframe at-rule', () => { 7 | // eslint-disable-next-line no-underscore-dangle 8 | supportedKeyframes.__Rewire__('prefix', { 9 | js: 'ms', 10 | css: '-ms-' 11 | }) 12 | expect(supportedKeyframes('@keyframes a')).to.be('@keyframes a') 13 | }) 14 | 15 | it('should prefix keyframe at-rule', () => { 16 | // eslint-disable-next-line no-underscore-dangle 17 | supportedKeyframes.__Rewire__('prefix', { 18 | js: 'Webkit', 19 | css: '-webkit-' 20 | }) 21 | expect(supportedKeyframes('@keyframes a')).to.be('@-webkit-keyframes a') 22 | // eslint-disable-next-line no-underscore-dangle 23 | supportedKeyframes.__ResetDependency__('prefix') 24 | }) 25 | 26 | it('should return keyframe at-rule as it is', () => { 27 | expect(supportedKeyframes('@-webkit-keyframes a')).to.be('@-webkit-keyframes a') 28 | }) 29 | }) 30 | }) 31 | -------------------------------------------------------------------------------- /src/supported-property.js: -------------------------------------------------------------------------------- 1 | import isInBrowser from 'is-in-browser' 2 | import {propertyDetectors, noPrefill} from './plugins/index' 3 | 4 | let el 5 | const cache = {} 6 | 7 | if (isInBrowser && document.documentElement) { 8 | el = document.createElement('p') 9 | 10 | // We test every property on vendor prefix requirement. 11 | // Once tested, result is cached. It gives us up to 70% perf boost. 12 | // http://jsperf.com/element-style-object-access-vs-plain-object 13 | // 14 | // Prefill cache with known css properties to reduce amount of 15 | // properties we need to feature test at runtime. 16 | // http://davidwalsh.name/vendor-prefix 17 | const computed = window.getComputedStyle(document.documentElement, '') 18 | for (const key in computed) { 19 | // eslint-disable-next-line no-restricted-globals 20 | if (!isNaN(key)) cache[computed[key]] = computed[key] 21 | } 22 | 23 | // Properties that cannot be correctly detected using the 24 | // cache prefill method. 25 | noPrefill.forEach(x => delete cache[x]) 26 | } 27 | 28 | /** 29 | * Test if a property is supported, returns supported property with vendor 30 | * prefix if required. Returns `false` if not supported. 31 | * 32 | * @param {String} prop dash separated 33 | * @param {Object} [options] 34 | * @return {String|Boolean} 35 | * @api public 36 | */ 37 | 38 | export default function supportedProperty(prop, options = {}) { 39 | // For server-side rendering. 40 | if (!el) return prop 41 | 42 | // Remove cache for benchmark tests or return property from the cache. 43 | if (process.env.NODE_ENV !== 'benchmark' && cache[prop] != null) { 44 | return cache[prop] 45 | } 46 | 47 | // Check if 'transition' or 'transform' natively supported in browser. 48 | if (prop === 'transition' || prop === 'transform') { 49 | options[prop] = prop in el.style 50 | } 51 | 52 | // Find a plugin for current prefix property. 53 | for (let i = 0; i < propertyDetectors.length; i++) { 54 | cache[prop] = propertyDetectors[i](prop, el.style, options) 55 | // Break loop, if value found. 56 | if (cache[prop]) break 57 | } 58 | 59 | // Reset styles for current property. 60 | // Firefox can even throw an error for invalid properties, e.g., "0". 61 | try { 62 | el.style[prop] = '' 63 | } catch (err) { 64 | return false 65 | } 66 | 67 | return cache[prop] 68 | } 69 | -------------------------------------------------------------------------------- /src/supported-property.test.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect.js' 2 | import {detectBrowser} from 'caniuse-support' 3 | 4 | import prefix from './prefix' 5 | import supportedProperty from './supported-property' 6 | import propertyPrefixFixture from '../tests/fixtures' 7 | 8 | const currentBrowser = detectBrowser(window.navigator.userAgent) 9 | 10 | describe('css-vendor', () => { 11 | describe('.supportedProperty()', () => { 12 | it('should not prefix', () => { 13 | expect(supportedProperty('display')).to.be('display') 14 | }) 15 | 16 | it('should not prefix already prefixed value', () => { 17 | expect(supportedProperty('-webkit-backdrop-filter')).to.be('-webkit-backdrop-filter') 18 | }) 19 | 20 | it('should not prefix custom CSS variables', () => { 21 | expect(supportedProperty('--padding-start')).to.be('--padding-start') 22 | }) 23 | 24 | const opts = {multiple: true} 25 | for (const property in propertyPrefixFixture) { 26 | it(`should prefix ${property} if needed [${currentBrowser.id} ${currentBrowser.version}]`, () => 27 | expect(supportedProperty(property, opts)).to.eql(propertyPrefixFixture[property])) 28 | } 29 | 30 | it('should prefix writing-mode if needed', () => { 31 | let isPrefixed = false 32 | if (prefix.js === 'Webkit' || (prefix.js === 'ms' && prefix.browser !== 'edge')) { 33 | isPrefixed = true 34 | } 35 | expect(supportedProperty('writing-mode')).to.be(`${isPrefixed ? prefix.css : ''}writing-mode`) 36 | }) 37 | 38 | it('should return false when property is unsupported', () => { 39 | expect(supportedProperty('xxx')).to.be(false) 40 | }) 41 | }) 42 | }) 43 | -------------------------------------------------------------------------------- /src/supported-value.js: -------------------------------------------------------------------------------- 1 | import isInBrowser from 'is-in-browser' 2 | import prefix from './prefix' 3 | import supportedProperty from './supported-property' 4 | 5 | const cache = {} 6 | const transitionProperties = { 7 | transition: 1, 8 | 'transition-property': 1, 9 | '-webkit-transition': 1, 10 | '-webkit-transition-property': 1 11 | } 12 | const transPropsRegExp = /(^\s*[\w-]+)|, (\s*[\w-]+)(?![^()]*\))/g 13 | let el 14 | 15 | /** 16 | * Returns prefixed value transition/transform if needed. 17 | * 18 | * @param {String} match 19 | * @param {String} p1 20 | * @param {String} p2 21 | * @return {String} 22 | * @api private 23 | */ 24 | function prefixTransitionCallback(match, p1, p2) { 25 | if (p1 === 'var') return 'var' 26 | if (p1 === 'all') return 'all' 27 | if (p2 === 'all') return ', all' 28 | const prefixedValue = p1 ? supportedProperty(p1) : `, ${supportedProperty(p2)}` 29 | if (!prefixedValue) return p1 || p2 30 | return prefixedValue 31 | } 32 | 33 | if (isInBrowser) el = document.createElement('p') 34 | 35 | /** 36 | * Returns prefixed value if needed. Returns `false` if value is not supported. 37 | * 38 | * @param {String} property 39 | * @param {String} value 40 | * @return {String|Boolean} 41 | * @api public 42 | */ 43 | 44 | export default function supportedValue(property, value) { 45 | // For server-side rendering. 46 | let prefixedValue = value 47 | if (!el || property === 'content') return value 48 | 49 | // It is a string or a number as a string like '1'. 50 | // We want only prefixable values here. 51 | // eslint-disable-next-line no-restricted-globals 52 | if (typeof prefixedValue !== 'string' || !isNaN(parseInt(prefixedValue, 10))) { 53 | return prefixedValue 54 | } 55 | 56 | // Create cache key for current value. 57 | const cacheKey = property + prefixedValue 58 | 59 | // Remove cache for benchmark tests or return value from cache. 60 | if (process.env.NODE_ENV !== 'benchmark' && cache[cacheKey] != null) { 61 | return cache[cacheKey] 62 | } 63 | 64 | // IE can even throw an error in some cases, for e.g. style.content = 'bar'. 65 | try { 66 | // Test value as it is. 67 | el.style[property] = prefixedValue 68 | } catch (err) { 69 | // Return false if value not supported. 70 | cache[cacheKey] = false 71 | return false 72 | } 73 | 74 | // If 'transition' or 'transition-property' property. 75 | if (transitionProperties[property]) { 76 | prefixedValue = prefixedValue.replace(transPropsRegExp, prefixTransitionCallback) 77 | } else if (el.style[property] === '') { 78 | // Value with a vendor prefix. 79 | prefixedValue = prefix.css + prefixedValue 80 | 81 | // Hardcode test to convert "flex" to "-ms-flexbox" for IE10. 82 | if (prefixedValue === '-ms-flex') el.style[property] = '-ms-flexbox' 83 | 84 | // Test prefixed value. 85 | el.style[property] = prefixedValue 86 | 87 | // Return false if value not supported. 88 | if (el.style[property] === '') { 89 | cache[cacheKey] = false 90 | return false 91 | } 92 | } 93 | 94 | // Reset styles for current property. 95 | el.style[property] = '' 96 | 97 | // Write current value to cache. 98 | cache[cacheKey] = prefixedValue 99 | 100 | return cache[cacheKey] 101 | } 102 | -------------------------------------------------------------------------------- /src/supported-value.test.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect.js' 2 | import {getSupport, detectBrowser} from 'caniuse-support' 3 | 4 | import propertyPrefixFixture from '../tests/fixtures' 5 | import prefix from './prefix' 6 | import supportedValue from './supported-value' 7 | 8 | describe('css-vendor', () => { 9 | describe('.supportedValue()', () => { 10 | it('should not prefix a simple value', () => { 11 | expect(supportedValue('display', 'none')).to.be('none') 12 | }) 13 | 14 | it('should not prefix a complex value', () => { 15 | const value = 'rgba(255, 255, 255, 1.0)' 16 | expect(supportedValue('color', value)).to.be(value) 17 | }) 18 | 19 | const {level: flexboxLevel, needPrefix: flexboxNeedPrefix} = getSupport( 20 | 'flexbox', 21 | detectBrowser(window.navigator.userAgent) 22 | ) 23 | if (flexboxLevel === 'full') { 24 | it('should prefix if needed for flex value', () => { 25 | const value = flexboxNeedPrefix ? `${prefix.css}flex` : 'flex' 26 | expect(supportedValue('display', 'flex')).to.be(value) 27 | }) 28 | } else { 29 | it.skip('flex is not fully supported in the current browser') 30 | } 31 | 32 | it('should return false when value is unknown', () => { 33 | expect(supportedValue('display', 'xxx')).to.be(false) 34 | }) 35 | 36 | it('should return value when property is "content"', () => { 37 | expect(supportedValue('content', 'bar')).to.be('bar') 38 | }) 39 | 40 | it('should return known transform value prefixed', () => { 41 | expect(supportedValue('transition', 'all 100ms ease, transform 200ms linear')).to.eql( 42 | `all 100ms ease, ${propertyPrefixFixture.transform} 200ms linear` 43 | ) 44 | }) 45 | 46 | it('should return dashed property value as it is', () => { 47 | expect(supportedValue('transition', 'max-height 300ms ease-in-out')).to.eql( 48 | 'max-height 300ms ease-in-out' 49 | ) 50 | }) 51 | 52 | it('should return dashed property value as it is', () => { 53 | expect(supportedValue('transition', 'ease-in-out 0.3s')).to.eql('ease-in-out 0.3s') 54 | }) 55 | 56 | it('should return custom CSS variable for transition property as it is', () => { 57 | expect(supportedValue('transition', 'var(--something)')).to.eql('var(--something)') 58 | }) 59 | 60 | it('should return custom CSS variables for transition property as they are', () => { 61 | expect(supportedValue('transition', 'width var(--width), height var(--height)')).to.eql( 62 | 'width var(--width), height var(--height)' 63 | ) 64 | }) 65 | 66 | it('should not break a complex transition value', () => { 67 | const value = 'margin 225ms cubic-bezier(0.0, 0, 0.2, 1) 0ms' 68 | expect(supportedValue('transition', value)).to.be(value) 69 | }) 70 | 71 | it('should prefix if needed for sticky value', () => { 72 | const {needPrefix: stickyNeedPrefix} = getSupport( 73 | 'css-sticky', 74 | detectBrowser(window.navigator.userAgent) 75 | ) 76 | const value = stickyNeedPrefix ? `${prefix.css}sticky` : 'sticky' 77 | expect(supportedValue('position', 'sticky')).to.be( 78 | prefix.js === 'ms' && prefix.browser !== 'edge' ? false : value 79 | ) 80 | }) 81 | }) 82 | }) 83 | -------------------------------------------------------------------------------- /src/utils/camelize.js: -------------------------------------------------------------------------------- 1 | const regExp = /[-\s]+(.)?/g 2 | 3 | /** 4 | * Replaces the letter with the capital letter 5 | * 6 | * @param {String} match 7 | * @param {String} c 8 | * @return {String} 9 | * @api private 10 | */ 11 | function toUpper(match, c) { 12 | return c ? c.toUpperCase() : '' 13 | } 14 | 15 | /** 16 | * Convert dash separated strings to camel-cased. 17 | * 18 | * @param {String} str 19 | * @return {String} 20 | * @api private 21 | */ 22 | export default function camelize(str) { 23 | return str.replace(regExp, toUpper) 24 | } 25 | -------------------------------------------------------------------------------- /src/utils/pascalize.js: -------------------------------------------------------------------------------- 1 | import camelize from './camelize' 2 | 3 | /** 4 | * Convert dash separated strings to pascal cased. 5 | * 6 | * @param {String} str 7 | * @return {String} 8 | * @api private 9 | */ 10 | export default function pascalize(str) { 11 | return camelize(`-${str}`) 12 | } 13 | -------------------------------------------------------------------------------- /tests.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 |