├── .babelrc ├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── docs ├── explanation.md └── receipe-theme.md ├── examples ├── .babelrc ├── App.js ├── Theme.js ├── advanced │ ├── .babelrc │ ├── Advanced.js │ ├── color │ │ ├── bezierEasing.less │ │ ├── colorPalette.less │ │ └── tinyColor.less │ └── javascript.less ├── import.less ├── index.html ├── index.js ├── package.json ├── variables.less ├── withCheckbox.js ├── withSlider.js └── yarn.lock ├── package.json ├── src ├── functions │ ├── boolean.js │ ├── color.js │ ├── index.js │ ├── math.js │ ├── number.js │ └── types.js ├── index.js ├── tree │ ├── Condition.js │ ├── Dimension.js │ ├── JavaScript.js │ ├── Negative.js │ ├── Variable.js │ ├── VariableNode.js │ ├── convert.js │ └── operate.js ├── utils │ └── detectors.js └── visitors │ ├── taggedTemplateExpressionVisitor.js │ ├── transpileLess.js │ └── utils.js ├── test ├── Browser │ ├── index.html │ ├── index.js │ ├── package.json │ └── yarn.lock ├── Component │ ├── .babelrc │ ├── BabelPluginStyledComponents │ │ ├── .babelrc │ │ ├── StyledComponent.test.js │ │ └── __snapshots__ │ │ │ └── StyledComponent.test.js.snap │ ├── BaseImportAbsolute │ │ ├── .babelrc │ │ ├── BaseImport.test.js │ │ ├── __snapshots__ │ │ │ └── BaseImport.test.js.snap │ │ └── foo │ │ │ ├── DirImport.test.js │ │ │ └── __snapshots__ │ │ │ └── DirImport.test.js.snap │ ├── BaseImportRelative │ │ ├── .babelrc │ │ ├── BaseImport.test.js │ │ └── __snapshots__ │ │ │ └── BaseImport.test.js.snap │ ├── Boolean.test.js │ ├── Color.test.js │ ├── Condition.test.js │ ├── Extend.test.js │ ├── Functional.test.js │ ├── ImportFromScopedNPM.test.js │ ├── Imports.test.js │ ├── Javascript.test.js │ ├── Less.test.js │ ├── List.test.js │ ├── Math.test.js │ ├── Mixins.test.js │ ├── NestedImports │ │ ├── .babelrc │ │ ├── NestedImports.test.js │ │ ├── __snapshots__ │ │ │ └── NestedImports.test.js.snap │ │ ├── reference.less │ │ ├── test.less │ │ ├── test2.less │ │ └── tmp.less │ ├── String.test.js │ ├── StyledComponent.test.js │ ├── Type.test.js │ ├── Variable.test.js │ ├── __snapshots__ │ │ ├── Boolean.test.js.snap │ │ ├── Color.test.js.snap │ │ ├── Condition.test.js.snap │ │ ├── Extend.test.js.snap │ │ ├── Functional.test.js.snap │ │ ├── ImportFromScopedNPM.test.js.snap │ │ ├── Imports.test.js.snap │ │ ├── Javascript.test.js.snap │ │ ├── Less.test.js.snap │ │ ├── List.test.js.snap │ │ ├── Math.test.js.snap │ │ ├── Mixins.test.js.snap │ │ ├── String.test.js.snap │ │ ├── StyledComponent.test.js.snap │ │ ├── Type.test.js.snap │ │ ├── Variable.test.js.snap │ │ └── createGlobalStyle.test.js.snap │ ├── base.less │ ├── color │ │ ├── bezierEasing.less │ │ ├── colorPalette.less │ │ └── tinyColor.less │ ├── createGlobalStyle.test.js │ ├── echoColor.less │ ├── import-scoped.less │ ├── import.less │ └── javascript.less └── packages │ └── scoped-package │ ├── import.less │ └── package.json └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env" 4 | ] 5 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.iml 3 | node_modules 4 | .cache 5 | dist 6 | .DS_Store 7 | *.tgz -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.iml 3 | .DS_Store 4 | test 5 | examples 6 | *.yml 7 | notes 8 | *.tgz 9 | yarn.lock -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | cache: 3 | directories: 4 | - ~/.npm 5 | node_js: 6 | - 8 7 | - 10 8 | - 12 9 | install: 10 | - yarn install --ignore-engines 11 | after_success: 12 | - npx travis-deploy-once "npx semantic-release" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Jean-Philippe Bergeron 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/jean343/styless.svg?branch=master)](https://travis-ci.org/jean343/styless) 2 | [![npm](https://img.shields.io/npm/v/babel-plugin-styless.svg)](https://www.npmjs.com/package/babel-plugin-styless) 3 | [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) 4 | [![style: styled-components](https://img.shields.io/badge/style-%F0%9F%92%85%20styled--components-orange.svg?colorB=daa357&colorA=db748e)](https://github.com/styled-components/styled-components) 5 | 6 | # :gem:Styless:gem: 7 | 8 | Styless enables [less](http://lesscss.org/) syntax in your [styled-components](https://www.styled-components.com) 9 | 10 | ## Installation 11 | ```sh 12 | $ yarn add --dev babel-plugin-styless 13 | ``` 14 | 15 | **.babelrc** 16 | 17 | ```json 18 | { 19 | "plugins": ["babel-plugin-styless"] 20 | } 21 | ``` 22 | 23 | Note that `styless` should appear before `babel-plugin-styled-components` if used. 24 | 25 | ## Key features 26 | - Simplifies the code 27 | 28 | use `@main` instead of `${props => props.theme.main}` 29 | 30 | - Uses variables directly in your styled components 31 | ```less 32 | @size: if(@small, 4px, 10px); 33 | ``` 34 | 35 | - Uses operations directly in your styled components 36 | 37 | use `@size * 3` instead of `${props => parseFloat(props.size) * 3 + "px"}` 38 | 39 | - Uses functions directly in your styled components. 40 | ```less 41 | color: darken(@highlight, 5%); 42 | ``` 43 | There is no need to import `darken`. 44 | 45 | - Supports `rgb`, `hsl` and `hsv` color spaces 46 | ```less 47 | color: hsv(90, 100%, 50%); 48 | ``` 49 | 50 | - Migrate less to styled-components seamlessly. 51 | 52 | There is no confusion when transitioning from less to styled-components caused by `width: 3px * 2`. 53 | 54 | - Supports variable overwritten 55 | ```less 56 | const Button = styled.button` 57 | @highlight: blue; // can be overwritten by theme or props 58 | background: darken(@highlight, 5%); // make green darken by 5% 59 | `; 60 | ``` 61 | 62 | ```jsx 63 | 64 | // green (set in props) overwrites red (set in theme) 65 | 66 | ``` 67 | 68 | - Supports imports and mixins 69 | ```less 70 | const Button = styled.button` 71 | @import (reference) "variables"; 72 | .bg-light-blue; 73 | `; 74 | ``` 75 | 76 | - Supports css props 77 | ```less 78 | 106 | 107 | 108 | ``` 109 | 110 | This is what you'll see in your browser :tada:, play with it on [codesandbox](https://codesandbox.io/s/p30ywzqkr7) 111 | 112 | ![](https://i.imgur.com/vb7wo7i.png) 113 | 114 | ## Advanced Styless component example 115 | ```less 116 | const Button = styled.button` 117 | @faded: fade(black, 21%); 118 | @size: if(@small, 4px, 10px); 119 | cursor: pointer; 120 | cursor: if(@disabled, not-allowed); 121 | color: hsv(0, 0%, 99%); 122 | padding: @size @size * 3; 123 | border: 1px solid @faded; 124 | border-bottom: 4px solid @faded; 125 | border-radius: ${4}px; 126 | text-shadow: 0 1px 0 @faded; 127 | background: linear-gradient(@highlight 0%, darken(@highlight, 5%) 100%); 128 | &:active { 129 | background: darken(@highlight, 10%); 130 | } 131 | `; 132 | ``` 133 | ```jsx 134 | // Notice that the @highlight variable is resolved from the theme, and overwritten from a props in the second button. 135 | 136 | 137 | 138 | 139 | 140 | ``` 141 | 142 | This is what you'll see in your browser :tada:, play with it on [codesandbox](https://codesandbox.io/s/6zq4jyo5qz) 143 | 144 | ![](https://i.imgur.com/01eETHm.png) 145 | 146 | 147 | *Note* that with [webstorm-styled-components](https://github.com/styled-components/webstorm-styled-components), 148 | we get syntax highlighting, color preview and ctrl+click access to variables! 149 | ![](https://i.imgur.com/t8Qw6ty.png") 150 | 151 | ## FAQ 152 | - How to refer to a `constants.less` file, see the receipe for [theme](docs/receipe-theme.md). 153 | - Cool, how does it work? Head over to the [explanations](docs/explanation.md). 154 | - Why less? The `@color` systax reduces the confusion from `$color` when comparing to the SC syntax `${props}`. If there is enough demand, a scss plugin could be created. 155 | - The styled-components mixins such as `${hover};` must be terminated with a semi colon. The following will not work. 156 | ```less 157 | const Button = styled.button` 158 | ${hover} 159 | color: red; 160 | `; 161 | ``` 162 | - How to import a less files for all components? As SC components are small in nature, it can be convenient to have a common less file be imported in all components. Add the following to your `.babelrc` to have `common.less` imported automatically. 163 | ``` 164 | [ 165 | "styless", 166 | { 167 | "cwd": "babelrc", 168 | "import": "../less/common.less" 169 | } 170 | ] 171 | ``` 172 | - Can this be used to load variable sheets such as Antd. 173 | Yes, in `.babelrc`, add the following or see https://stackoverflow.com/a/59472390/3666615 for more detals. 174 | ``` 175 | [ 176 | "styless", 177 | { 178 | "import": "~antd/lib/style/themes/default.less", 179 | "lessOptions": { 180 | "javascriptEnabled": true 181 | } 182 | } 183 | ] 184 | ``` 185 | -------------------------------------------------------------------------------- /docs/explanation.md: -------------------------------------------------------------------------------- 1 | ## Cool, how does it work :question: 2 | The {less} parser is used to generate an abstract syntax tree (AST) of the styled-components source, 3 | all less functions that would operate on variables are modified to generate dynamic code. 4 | 5 | For example, `color: darken(@color, 15%)`; is converted to the following snippet. Note that it is using `tinycolor2` for the color conversion, 6 | and that `@color` is resolved in `props` and `props.theme`. See the [darken](https://github.com/jean343/styless/blob/master/src/functions/color.js#L91) code. 7 | ```jsx 8 | color: require('tinycolor2')([props["color"],(props.theme || {})["color"]].filter(v => v !== void 0)[0]).darken(parseFloat("15%")).toHex8String(); 9 | ``` 10 | 11 | As this code is executed at run time, I am looking at ways to shorten the code and make it execute faster. Send a PR if you have an :bulb:! 12 | 13 | ---- 14 | 15 | An if statement `if(@c, green, red);` would be converted to the following, see the [boolean](https://github.com/jean343/styless/blob/master/src/functions/boolean.js#L9) code. 16 | ```jsx 17 | ([props["c"],(props.theme || {})["c"]].filter(v => v !== void 0)[0] === true) ? "green" : "red" 18 | ``` -------------------------------------------------------------------------------- /docs/receipe-theme.md: -------------------------------------------------------------------------------- 1 | ## To use a less constants file in your theme 2 | 3 | Styless is using the `ThemeProvider` from `styled-components`, and as we can see in the following example, the theme is a JSON object defining 4 | the less variables. 5 | 6 | ```jsx 7 | 8 | 9 | 10 | ``` 11 | 12 | One can use [less-vars-to-js](https://www.npmjs.com/package/less-vars-to-js) to parse a less file congaing variables and populate the theme. 13 | 14 | ```jsx 15 | import {ThemeProvider} from 'styled-components'; 16 | import lessToJs from 'less-vars-to-js'; 17 | import fs from 'fs'; 18 | 19 | const constants = lessToJs(fs.readFileSync(__dirname + "/variables.less", "utf8"), {resolveVariables: true, stripPrefix: true}); 20 | 21 | 22 | {this.props.children} 23 | 24 | ``` 25 | 26 | The full example can be seen on [github](https://github.com/jean343/styless/blob/master/examples/Theme.js). -------------------------------------------------------------------------------- /examples/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "styless", 4 | "@babel/plugin-proposal-class-properties" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /examples/App.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import styled from 'styled-components'; 3 | import Advanced from './advanced/Advanced'; 4 | 5 | const App = styled.div` 6 | color: @text; 7 | `; 8 | 9 | const Title = styled.h1` 10 | font-size: 1.5em; 11 | color: @highlight; 12 | b { 13 | color: lighten(@highlight, 20%); 14 | } 15 | `; 16 | 17 | const Wrapper = styled.section` 18 | text-align: center; 19 | padding: 4em; 20 | background-color: @background; 21 | `; 22 | 23 | const Button = styled.button` 24 | @faded: fade(black, 21%); 25 | @size: if(@small, 4px, 10px); 26 | cursor: pointer; 27 | cursor: if(@disabled, not-allowed); 28 | color: hsv(0, 0%, 99%); 29 | padding: @size @size * 3; 30 | border: 1px solid @faded; 31 | border-bottom: 4px solid @faded; 32 | border-radius: ${4}px; 33 | text-shadow: 0 1px 0 @faded; 34 | background: linear-gradient(@highlight 0%, darken(@highlight, 5%) 100%); 35 | &:active { 36 | background: darken(@highlight, 10%); 37 | } 38 | `; 39 | 40 | export default class extends Component { 41 | state = {open: false}; 42 | onClick = e => this.setState({open: !this.state.open}); 43 | 44 | render() { 45 | return 46 | 47 | 48 | Hello World, this is my first styled <b>component</b>! 49 | 50 | 51 | Hello World, this is my first styled <b>component</b>! 52 | 53 | 54 | {this.state.open && } 55 | 56 | 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /examples/Theme.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {ThemeProvider} from 'styled-components'; 3 | import lessToJs from 'less-vars-to-js'; 4 | import fs from 'fs'; 5 | 6 | const constants = lessToJs(fs.readFileSync(__dirname + "/variables.less", "utf8"), {resolveVariables: true, stripPrefix: true}); 7 | 8 | export default class extends Component { 9 | render() { 10 | return 11 | {this.props.children} 12 | 13 | } 14 | } -------------------------------------------------------------------------------- /examples/advanced/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["styless", { "lessOptions": { "javascriptEnabled": true } }], 4 | "@babel/plugin-proposal-class-properties" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /examples/advanced/Advanced.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import styled from 'styled-components'; 3 | import withCheckbox from '../withCheckbox'; 4 | import withSlider from '../withSlider'; 5 | 6 | const Style = styled.div` 7 | margin-top: 20px; 8 | `; 9 | const S = styled.div` 10 | display: flex; 11 | justify-content: center; 12 | align-content: center; 13 | width: 800px; 14 | margin: 8px auto; 15 | padding: 8px; 16 | `; 17 | const Content = styled.div` 18 | text-align: left; 19 | white-space: pre; 20 | `; 21 | 22 | const samples = { 23 | "use javascript function": S.extend` 24 | @import "./javascript.less"; 25 | color: @echoColor(green); 26 | background: @primary-1; 27 | border: 1px solid @primary-2; 28 | `, 29 | "width: 700px + @value;": withSlider(S.extend`background-color: @highlight; width: 700px + @value;`), 30 | "width: ${props => 700 + parseFloat(props.value)}px;": withSlider(S.extend`background-color: @highlight; width: ${props => 700 + parseFloat(props.value)}px;`), 31 | "background-color: darken(@highlight, 30%);": S.extend`background-color: darken(@highlight, 30%);`, 32 | "@import (reference) \"variables\";\n.bg-light-blue;": S.extend` 33 | @import (reference) "../variables"; 34 | .bg-light-blue;`, 35 | "@local: palevioletred;\n@width: if(@checked, 20px, 0);\nbackground-color: @local;\nborder-radius: @width;": withCheckbox(S.extend` 36 | @local: palevioletred; 37 | @width: if(@checked, 20px, 0); 38 | background-color: @local; 39 | border-radius: @width; 40 | `), 41 | "background-color: darken(hsl(90, 80%, 50%), 20%);": S.extend`background-color: darken(hsl(90, 80%, 50%), 20%);`, 42 | "background-color: darken( rgb( 251 , 90 , 79 ), 20% ) ;": S.extend`background-color: darken( rgb( 251 , 90 , 79 ), 20% ) ;`, 43 | "background-color: lighten(darken(hsl(90, 80%, 50%), 20%), 40%);": S.extend`background-color: lighten(darken(hsl(90, 80%, 50%), 20%), 40%);`, 44 | "background-color: if(@checked, DeepPink, palevioletred)": withCheckbox(S.extend`background-color: if(@checked, DeepPink, palevioletred);`), 45 | "background-color: if(@checked, darken(@highlight, 30%), @highlight);": withCheckbox(S.extend`background-color: if(@checked, darken(@highlight, 30%), @highlight);`), 46 | "margin: if(@checked, 16px auto);": withCheckbox(S.extend`margin: if(@checked, 16px auto);`), 47 | "@bg: if(@checked, @navy, @aqua);\n@bg-light: boolean(luma(@bg) > 50%);\nbackground: @bg;\ncolor: if(@bg-light, black, white);": withCheckbox(S.extend` 48 | @bg: if(@checked, @navy, lightgray); 49 | @bg-light: boolean(luma(@bg) > 50%); 50 | background: @bg; 51 | color: if(@bg-light, black, white); 52 | `), 53 | "font-size: ceil(20.5px);": S.extend`font-size: ceil(20.5px);`, 54 | "background-color: darken(hsl(90, 80%, 50%), @value);": withSlider(S.extend`background-color: darken(hsl(90, 80%, 50%), @value);`), 55 | "background-color: fade(@highlight, @value);": withSlider(S.extend`background-color: fade(@highlight, @value);`), 56 | "background-color: spin(@highlight, @value);": withSlider(S.extend`background-color: spin(@highlight, @value);`, {min: -180, max: 180}), 57 | }; 58 | 59 | export default class extends Component { 60 | render() { 61 | return 69 | } 70 | } -------------------------------------------------------------------------------- /examples/advanced/color/bezierEasing.less: -------------------------------------------------------------------------------- 1 | /* stylelint-disable declaration-bang-space-before,no-duplicate-selectors */ 2 | .bezierEasingMixin() { 3 | @functions: ~`(function() { 4 | var NEWTON_ITERATIONS = 4; 5 | var NEWTON_MIN_SLOPE = 0.001; 6 | var SUBDIVISION_PRECISION = 0.0000001; 7 | var SUBDIVISION_MAX_ITERATIONS = 10; 8 | 9 | var kSplineTableSize = 11; 10 | var kSampleStepSize = 1.0 / (kSplineTableSize - 1.0); 11 | 12 | var float32ArraySupported = typeof Float32Array === 'function'; 13 | 14 | function A (aA1, aA2) { return 1.0 - 3.0 * aA2 + 3.0 * aA1; } 15 | function B (aA1, aA2) { return 3.0 * aA2 - 6.0 * aA1; } 16 | function C (aA1) { return 3.0 * aA1; } 17 | 18 | // Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2. 19 | function calcBezier (aT, aA1, aA2) { return ((A(aA1, aA2) * aT + B(aA1, aA2)) * aT + C(aA1)) * aT; } 20 | 21 | // Returns dx/dt given t, x1, and x2, or dy/dt given t, y1, and y2. 22 | function getSlope (aT, aA1, aA2) { return 3.0 * A(aA1, aA2) * aT * aT + 2.0 * B(aA1, aA2) * aT + C(aA1); } 23 | 24 | function binarySubdivide (aX, aA, aB, mX1, mX2) { 25 | var currentX, currentT, i = 0; 26 | do { 27 | currentT = aA + (aB - aA) / 2.0; 28 | currentX = calcBezier(currentT, mX1, mX2) - aX; 29 | if (currentX > 0.0) { 30 | aB = currentT; 31 | } else { 32 | aA = currentT; 33 | } 34 | } while (Math.abs(currentX) > SUBDIVISION_PRECISION && ++i < SUBDIVISION_MAX_ITERATIONS); 35 | return currentT; 36 | } 37 | 38 | function newtonRaphsonIterate (aX, aGuessT, mX1, mX2) { 39 | for (var i = 0; i < NEWTON_ITERATIONS; ++i) { 40 | var currentSlope = getSlope(aGuessT, mX1, mX2); 41 | if (currentSlope === 0.0) { 42 | return aGuessT; 43 | } 44 | var currentX = calcBezier(aGuessT, mX1, mX2) - aX; 45 | aGuessT -= currentX / currentSlope; 46 | } 47 | return aGuessT; 48 | } 49 | 50 | var BezierEasing = function (mX1, mY1, mX2, mY2) { 51 | if (!(0 <= mX1 && mX1 <= 1 && 0 <= mX2 && mX2 <= 1)) { 52 | throw new Error('bezier x values must be in [0, 1] range'); 53 | } 54 | 55 | // Precompute samples table 56 | var sampleValues = float32ArraySupported ? new Float32Array(kSplineTableSize) : new Array(kSplineTableSize); 57 | if (mX1 !== mY1 || mX2 !== mY2) { 58 | for (var i = 0; i < kSplineTableSize; ++i) { 59 | sampleValues[i] = calcBezier(i * kSampleStepSize, mX1, mX2); 60 | } 61 | } 62 | 63 | function getTForX (aX) { 64 | var intervalStart = 0.0; 65 | var currentSample = 1; 66 | var lastSample = kSplineTableSize - 1; 67 | 68 | for (; currentSample !== lastSample && sampleValues[currentSample] <= aX; ++currentSample) { 69 | intervalStart += kSampleStepSize; 70 | } 71 | --currentSample; 72 | 73 | // Interpolate to provide an initial guess for t 74 | var dist = (aX - sampleValues[currentSample]) / (sampleValues[currentSample + 1] - sampleValues[currentSample]); 75 | var guessForT = intervalStart + dist * kSampleStepSize; 76 | 77 | var initialSlope = getSlope(guessForT, mX1, mX2); 78 | if (initialSlope >= NEWTON_MIN_SLOPE) { 79 | return newtonRaphsonIterate(aX, guessForT, mX1, mX2); 80 | } else if (initialSlope === 0.0) { 81 | return guessForT; 82 | } else { 83 | return binarySubdivide(aX, intervalStart, intervalStart + kSampleStepSize, mX1, mX2); 84 | } 85 | } 86 | 87 | return function BezierEasing (x) { 88 | if (mX1 === mY1 && mX2 === mY2) { 89 | return x; // linear 90 | } 91 | // Because JavaScript number are imprecise, we should guarantee the extremes are right. 92 | if (x === 0) { 93 | return 0; 94 | } 95 | if (x === 1) { 96 | return 1; 97 | } 98 | return calcBezier(getTForX(x), mY1, mY2); 99 | }; 100 | }; 101 | 102 | this.colorEasing = BezierEasing(0.26, 0.09, 0.37, 0.18); 103 | // less 3 requires a return 104 | return ''; 105 | })()`; 106 | } 107 | // It is hacky way to make this function will be compiled preferentially by less 108 | // resolve error: `ReferenceError: colorPalette is not defined` 109 | // https://github.com/ant-design/ant-motion/issues/44 110 | .bezierEasingMixin(); 111 | -------------------------------------------------------------------------------- /examples/advanced/color/colorPalette.less: -------------------------------------------------------------------------------- 1 | /* stylelint-disable no-duplicate-selectors */ 2 | @import "bezierEasing"; 3 | @import "tinyColor"; 4 | 5 | // We create a very complex algorithm which take the place of original tint/shade color system 6 | // to make sure no one can understand it 👻 7 | // and create an entire color palette magicly by inputing just a single primary color. 8 | // We are using bezier-curve easing function and some color manipulations like tint/shade/darken/spin 9 | .colorPaletteMixin() { 10 | @functions: ~`(function() { 11 | var hueStep = 2; 12 | var saturationStep = 16; 13 | var saturationStep2 = 5; 14 | var brightnessStep1 = 5; 15 | var brightnessStep2 = 15; 16 | var lightColorCount = 5; 17 | var darkColorCount = 4; 18 | 19 | var getHue = function(hsv, i, isLight) { 20 | var hue; 21 | if (hsv.h >= 60 && hsv.h <= 240) { 22 | hue = isLight ? hsv.h - hueStep * i : hsv.h + hueStep * i; 23 | } else { 24 | hue = isLight ? hsv.h + hueStep * i : hsv.h - hueStep * i; 25 | } 26 | if (hue < 0) { 27 | hue += 360; 28 | } else if (hue >= 360) { 29 | hue -= 360; 30 | } 31 | return Math.round(hue); 32 | }; 33 | var getSaturation = function(hsv, i, isLight) { 34 | var saturation; 35 | if (isLight) { 36 | saturation = Math.round(hsv.s * 100) - saturationStep * i; 37 | } else if (i == darkColorCount) { 38 | saturation = Math.round(hsv.s * 100) + saturationStep; 39 | } else { 40 | saturation = Math.round(hsv.s * 100) + saturationStep2 * i; 41 | } 42 | if (saturation > 100) { 43 | saturation = 100; 44 | } 45 | if (isLight && i === lightColorCount && saturation > 10) { 46 | saturation = 10; 47 | } 48 | if (saturation < 6) { 49 | saturation = 6; 50 | } 51 | return Math.round(saturation); 52 | }; 53 | var getValue = function(hsv, i, isLight) { 54 | if (isLight) { 55 | return Math.round(hsv.v * 100) + brightnessStep1 * i; 56 | } 57 | return Math.round(hsv.v * 100) - brightnessStep2 * i; 58 | }; 59 | 60 | this.colorPalette = function(color, index) { 61 | var isLight = index <= 6; 62 | var hsv = tinycolor(color).toHsv(); 63 | var i = isLight ? lightColorCount + 1 - index : index - lightColorCount - 1; 64 | return tinycolor({ 65 | h: getHue(hsv, i, isLight), 66 | s: getSaturation(hsv, i, isLight), 67 | v: getValue(hsv, i, isLight), 68 | }).toHexString(); 69 | }; 70 | })()`; 71 | } 72 | // It is hacky way to make this function will be compiled preferentially by less 73 | // resolve error: `ReferenceError: colorPalette is not defined` 74 | // https://github.com/ant-design/ant-motion/issues/44 75 | .colorPaletteMixin(); 76 | -------------------------------------------------------------------------------- /examples/advanced/color/tinyColor.less: -------------------------------------------------------------------------------- 1 | /* stylelint-disable declaration-bang-space-before,no-duplicate-selectors,string-no-newline */ 2 | .tinyColorMixin() { 3 | @functions: ~`(function() { 4 | // TinyColor v1.4.1 5 | // https://github.com/bgrins/TinyColor 6 | // 2016-07-07, Brian Grinstead, MIT License 7 | var trimLeft = /^\s+/, 8 | trimRight = /\s+$/, 9 | tinyCounter = 0, 10 | mathRound = Math.round, 11 | mathMin = Math.min, 12 | mathMax = Math.max; 13 | 14 | function tinycolor (color, opts) { 15 | 16 | color = (color) ? color : ''; 17 | opts = opts || { }; 18 | 19 | // If input is already a tinycolor, return itself 20 | if (color instanceof tinycolor) { 21 | return color; 22 | } 23 | // If we are called as a function, call using new instead 24 | if (!(this instanceof tinycolor)) { 25 | return new tinycolor(color, opts); 26 | } 27 | 28 | var rgb = inputToRGB(color); 29 | this._originalInput = color, 30 | this._r = rgb.r, 31 | this._g = rgb.g, 32 | this._b = rgb.b, 33 | this._a = rgb.a, 34 | this._roundA = mathRound(100*this._a) / 100, 35 | this._format = opts.format || rgb.format; 36 | this._gradientType = opts.gradientType; 37 | 38 | // Don't let the range of [0,255] come back in [0,1]. 39 | // Potentially lose a little bit of precision here, but will fix issues where 40 | // .5 gets interpreted as half of the total, instead of half of 1 41 | // If it was supposed to be 128, this was already taken care of by inputToRgb 42 | if (this._r < 1) { this._r = mathRound(this._r); } 43 | if (this._g < 1) { this._g = mathRound(this._g); } 44 | if (this._b < 1) { this._b = mathRound(this._b); } 45 | 46 | this._ok = rgb.ok; 47 | this._tc_id = tinyCounter++; 48 | } 49 | 50 | tinycolor.prototype = { 51 | toHsv: function() { 52 | var hsv = rgbToHsv(this._r, this._g, this._b); 53 | return { h: hsv.h * 360, s: hsv.s, v: hsv.v, a: this._a }; 54 | }, 55 | toHex: function(allow3Char) { 56 | return rgbToHex(this._r, this._g, this._b, allow3Char); 57 | }, 58 | toHexString: function(allow3Char) { 59 | return '#' + this.toHex(allow3Char); 60 | } 61 | }; 62 | 63 | // rgbToHsv 64 | // Converts an RGB color value to HSV 65 | // *Assumes:* r, g, and b are contained in the set [0, 255] or [0, 1] 66 | // *Returns:* { h, s, v } in [0,1] 67 | function rgbToHsv(r, g, b) { 68 | 69 | r = bound01(r, 255); 70 | g = bound01(g, 255); 71 | b = bound01(b, 255); 72 | 73 | var max = mathMax(r, g, b), min = mathMin(r, g, b); 74 | var h, s, v = max; 75 | 76 | var d = max - min; 77 | s = max === 0 ? 0 : d / max; 78 | 79 | if(max == min) { 80 | h = 0; // achromatic 81 | } 82 | else { 83 | switch(max) { 84 | case r: h = (g - b) / d + (g < b ? 6 : 0); break; 85 | case g: h = (b - r) / d + 2; break; 86 | case b: h = (r - g) / d + 4; break; 87 | } 88 | h /= 6; 89 | } 90 | return { h: h, s: s, v: v }; 91 | } 92 | 93 | // rgbToHex 94 | // Converts an RGB color to hex 95 | // Assumes r, g, and b are contained in the set [0, 255] 96 | // Returns a 3 or 6 character hex 97 | function rgbToHex(r, g, b, allow3Char) { 98 | 99 | var hex = [ 100 | pad2(mathRound(r).toString(16)), 101 | pad2(mathRound(g).toString(16)), 102 | pad2(mathRound(b).toString(16)) 103 | ]; 104 | 105 | // Return a 3 character hex if possible 106 | if (allow3Char && hex[0].charAt(0) == hex[0].charAt(1) && hex[1].charAt(0) == hex[1].charAt(1) && hex[2].charAt(0) == hex[2].charAt(1)) { 107 | return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0); 108 | } 109 | 110 | return hex.join(""); 111 | } 112 | 113 | 114 | // Take input from [0, n] and return it as [0, 1] 115 | function bound01(n, max) { 116 | if (isOnePointZero(n)) { n = "100%"; } 117 | 118 | var processPercent = isPercentage(n); 119 | n = mathMin(max, mathMax(0, parseFloat(n))); 120 | 121 | // Automatically convert percentage into number 122 | if (processPercent) { 123 | n = parseInt(n * max, 10) / 100; 124 | } 125 | 126 | // Handle floating point rounding errors 127 | if ((Math.abs(n - max) < 0.000001)) { 128 | return 1; 129 | } 130 | 131 | // Convert into [0, 1] range if it isn't already 132 | return (n % max) / parseFloat(max); 133 | } 134 | 135 | // Given a string or object, convert that input to RGB 136 | // Possible string inputs: 137 | // 138 | // "red" 139 | // "#f00" or "f00" 140 | // "#ff0000" or "ff0000" 141 | // "#ff000000" or "ff000000" 142 | // "rgb 255 0 0" or "rgb (255, 0, 0)" 143 | // "rgb 1.0 0 0" or "rgb (1, 0, 0)" 144 | // "rgba (255, 0, 0, 1)" or "rgba 255, 0, 0, 1" 145 | // "rgba (1.0, 0, 0, 1)" or "rgba 1.0, 0, 0, 1" 146 | // "hsl(0, 100%, 50%)" or "hsl 0 100% 50%" 147 | // "hsla(0, 100%, 50%, 1)" or "hsla 0 100% 50%, 1" 148 | // "hsv(0, 100%, 100%)" or "hsv 0 100% 100%" 149 | // 150 | function inputToRGB(color) { 151 | 152 | var rgb = { r: 0, g: 0, b: 0 }; 153 | var a = 1; 154 | var s = null; 155 | var v = null; 156 | var l = null; 157 | var ok = false; 158 | var format = false; 159 | 160 | if (typeof color == "string") { 161 | color = stringInputToObject(color); 162 | } 163 | 164 | if (typeof color == "object") { 165 | if (isValidCSSUnit(color.r) && isValidCSSUnit(color.g) && isValidCSSUnit(color.b)) { 166 | rgb = rgbToRgb(color.r, color.g, color.b); 167 | ok = true; 168 | format = String(color.r).substr(-1) === "%" ? "prgb" : "rgb"; 169 | } 170 | else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.v)) { 171 | s = convertToPercentage(color.s); 172 | v = convertToPercentage(color.v); 173 | rgb = hsvToRgb(color.h, s, v); 174 | ok = true; 175 | format = "hsv"; 176 | } 177 | else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.l)) { 178 | s = convertToPercentage(color.s); 179 | l = convertToPercentage(color.l); 180 | rgb = hslToRgb(color.h, s, l); 181 | ok = true; 182 | format = "hsl"; 183 | } 184 | 185 | if (color.hasOwnProperty("a")) { 186 | a = color.a; 187 | } 188 | } 189 | 190 | a = boundAlpha(a); 191 | 192 | return { 193 | ok: ok, 194 | format: color.format || format, 195 | r: mathMin(255, mathMax(rgb.r, 0)), 196 | g: mathMin(255, mathMax(rgb.g, 0)), 197 | b: mathMin(255, mathMax(rgb.b, 0)), 198 | a: a 199 | }; 200 | } 201 | 202 | // Conversion Functions 203 | // -------------------- 204 | 205 | // rgbToHsl, rgbToHsv, hslToRgb, hsvToRgb modified from: 206 | // 207 | 208 | // rgbToRgb 209 | // Handle bounds / percentage checking to conform to CSS color spec 210 | // 211 | // *Assumes:* r, g, b in [0, 255] or [0, 1] 212 | // *Returns:* { r, g, b } in [0, 255] 213 | function rgbToRgb(r, g, b){ 214 | return { 215 | r: bound01(r, 255) * 255, 216 | g: bound01(g, 255) * 255, 217 | b: bound01(b, 255) * 255 218 | }; 219 | } 220 | 221 | // hslToRgb 222 | // Converts an HSL color value to RGB. 223 | // *Assumes:* h is contained in [0, 1] or [0, 360] and s and l are contained [0, 1] or [0, 100] 224 | // *Returns:* { r, g, b } in the set [0, 255] 225 | function hslToRgb(h, s, l) { 226 | var r, g, b; 227 | 228 | h = bound01(h, 360); 229 | s = bound01(s, 100); 230 | l = bound01(l, 100); 231 | 232 | function hue2rgb(p, q, t) { 233 | if(t < 0) t += 1; 234 | if(t > 1) t -= 1; 235 | if(t < 1/6) return p + (q - p) * 6 * t; 236 | if(t < 1/2) return q; 237 | if(t < 2/3) return p + (q - p) * (2/3 - t) * 6; 238 | return p; 239 | } 240 | 241 | if(s === 0) { 242 | r = g = b = l; // achromatic 243 | } 244 | else { 245 | var q = l < 0.5 ? l * (1 + s) : l + s - l * s; 246 | var p = 2 * l - q; 247 | r = hue2rgb(p, q, h + 1/3); 248 | g = hue2rgb(p, q, h); 249 | b = hue2rgb(p, q, h - 1/3); 250 | } 251 | 252 | return { r: r * 255, g: g * 255, b: b * 255 }; 253 | } 254 | 255 | // hsvToRgb 256 | // Converts an HSV color value to RGB. 257 | // *Assumes:* h is contained in [0, 1] or [0, 360] and s and v are contained in [0, 1] or [0, 100] 258 | // *Returns:* { r, g, b } in the set [0, 255] 259 | function hsvToRgb(h, s, v) { 260 | 261 | h = bound01(h, 360) * 6; 262 | s = bound01(s, 100); 263 | v = bound01(v, 100); 264 | 265 | var i = Math.floor(h), 266 | f = h - i, 267 | p = v * (1 - s), 268 | q = v * (1 - f * s), 269 | t = v * (1 - (1 - f) * s), 270 | mod = i % 6, 271 | r = [v, q, p, p, t, v][mod], 272 | g = [t, v, v, q, p, p][mod], 273 | b = [p, p, t, v, v, q][mod]; 274 | 275 | return { r: r * 255, g: g * 255, b: b * 255 }; 276 | } 277 | 278 | // Big List of Colors 279 | // ------------------ 280 | // 281 | var names = tinycolor.names = { 282 | aliceblue: "f0f8ff", 283 | antiquewhite: "faebd7", 284 | aqua: "0ff", 285 | aquamarine: "7fffd4", 286 | azure: "f0ffff", 287 | beige: "f5f5dc", 288 | bisque: "ffe4c4", 289 | black: "000", 290 | blanchedalmond: "ffebcd", 291 | blue: "00f", 292 | blueviolet: "8a2be2", 293 | brown: "a52a2a", 294 | burlywood: "deb887", 295 | burntsienna: "ea7e5d", 296 | cadetblue: "5f9ea0", 297 | chartreuse: "7fff00", 298 | chocolate: "d2691e", 299 | coral: "ff7f50", 300 | cornflowerblue: "6495ed", 301 | cornsilk: "fff8dc", 302 | crimson: "dc143c", 303 | cyan: "0ff", 304 | darkblue: "00008b", 305 | darkcyan: "008b8b", 306 | darkgoldenrod: "b8860b", 307 | darkgray: "a9a9a9", 308 | darkgreen: "006400", 309 | darkgrey: "a9a9a9", 310 | darkkhaki: "bdb76b", 311 | darkmagenta: "8b008b", 312 | darkolivegreen: "556b2f", 313 | darkorange: "ff8c00", 314 | darkorchid: "9932cc", 315 | darkred: "8b0000", 316 | darksalmon: "e9967a", 317 | darkseagreen: "8fbc8f", 318 | darkslateblue: "483d8b", 319 | darkslategray: "2f4f4f", 320 | darkslategrey: "2f4f4f", 321 | darkturquoise: "00ced1", 322 | darkviolet: "9400d3", 323 | deeppink: "ff1493", 324 | deepskyblue: "00bfff", 325 | dimgray: "696969", 326 | dimgrey: "696969", 327 | dodgerblue: "1e90ff", 328 | firebrick: "b22222", 329 | floralwhite: "fffaf0", 330 | forestgreen: "228b22", 331 | fuchsia: "f0f", 332 | gainsboro: "dcdcdc", 333 | ghostwhite: "f8f8ff", 334 | gold: "ffd700", 335 | goldenrod: "daa520", 336 | gray: "808080", 337 | green: "008000", 338 | greenyellow: "adff2f", 339 | grey: "808080", 340 | honeydew: "f0fff0", 341 | hotpink: "ff69b4", 342 | indianred: "cd5c5c", 343 | indigo: "4b0082", 344 | ivory: "fffff0", 345 | khaki: "f0e68c", 346 | lavender: "e6e6fa", 347 | lavenderblush: "fff0f5", 348 | lawngreen: "7cfc00", 349 | lemonchiffon: "fffacd", 350 | lightblue: "add8e6", 351 | lightcoral: "f08080", 352 | lightcyan: "e0ffff", 353 | lightgoldenrodyellow: "fafad2", 354 | lightgray: "d3d3d3", 355 | lightgreen: "90ee90", 356 | lightgrey: "d3d3d3", 357 | lightpink: "ffb6c1", 358 | lightsalmon: "ffa07a", 359 | lightseagreen: "20b2aa", 360 | lightskyblue: "87cefa", 361 | lightslategray: "789", 362 | lightslategrey: "789", 363 | lightsteelblue: "b0c4de", 364 | lightyellow: "ffffe0", 365 | lime: "0f0", 366 | limegreen: "32cd32", 367 | linen: "faf0e6", 368 | magenta: "f0f", 369 | maroon: "800000", 370 | mediumaquamarine: "66cdaa", 371 | mediumblue: "0000cd", 372 | mediumorchid: "ba55d3", 373 | mediumpurple: "9370db", 374 | mediumseagreen: "3cb371", 375 | mediumslateblue: "7b68ee", 376 | mediumspringgreen: "00fa9a", 377 | mediumturquoise: "48d1cc", 378 | mediumvioletred: "c71585", 379 | midnightblue: "191970", 380 | mintcream: "f5fffa", 381 | mistyrose: "ffe4e1", 382 | moccasin: "ffe4b5", 383 | navajowhite: "ffdead", 384 | navy: "000080", 385 | oldlace: "fdf5e6", 386 | olive: "808000", 387 | olivedrab: "6b8e23", 388 | orange: "ffa500", 389 | orangered: "ff4500", 390 | orchid: "da70d6", 391 | palegoldenrod: "eee8aa", 392 | palegreen: "98fb98", 393 | paleturquoise: "afeeee", 394 | palevioletred: "db7093", 395 | papayawhip: "ffefd5", 396 | peachpuff: "ffdab9", 397 | peru: "cd853f", 398 | pink: "ffc0cb", 399 | plum: "dda0dd", 400 | powderblue: "b0e0e6", 401 | purple: "800080", 402 | rebeccapurple: "663399", 403 | red: "f00", 404 | rosybrown: "bc8f8f", 405 | royalblue: "4169e1", 406 | saddlebrown: "8b4513", 407 | salmon: "fa8072", 408 | sandybrown: "f4a460", 409 | seagreen: "2e8b57", 410 | seashell: "fff5ee", 411 | sienna: "a0522d", 412 | silver: "c0c0c0", 413 | skyblue: "87ceeb", 414 | slateblue: "6a5acd", 415 | slategray: "708090", 416 | slategrey: "708090", 417 | snow: "fffafa", 418 | springgreen: "00ff7f", 419 | steelblue: "4682b4", 420 | tan: "d2b48c", 421 | teal: "008080", 422 | thistle: "d8bfd8", 423 | tomato: "ff6347", 424 | turquoise: "40e0d0", 425 | violet: "ee82ee", 426 | wheat: "f5deb3", 427 | white: "fff", 428 | whitesmoke: "f5f5f5", 429 | yellow: "ff0", 430 | yellowgreen: "9acd32" 431 | }; 432 | 433 | // Utilities 434 | // --------- 435 | 436 | // Return a valid alpha value [0,1] with all invalid values being set to 1 437 | function boundAlpha(a) { 438 | a = parseFloat(a); 439 | 440 | if (isNaN(a) || a < 0 || a > 1) { 441 | a = 1; 442 | } 443 | 444 | return a; 445 | } 446 | 447 | // Parse a base-16 hex value into a base-10 integer 448 | function parseIntFromHex(val) { 449 | return parseInt(val, 16); 450 | } 451 | 452 | // Converts a hex value to a decimal 453 | function convertHexToDecimal(h) { 454 | return (parseIntFromHex(h) / 255); 455 | } 456 | 457 | // Need to handle 1.0 as 100%, since once it is a number, there is no difference between it and 1 458 | // 459 | function isOnePointZero(n) { 460 | return typeof n == "string" && n.indexOf('.') != -1 && parseFloat(n) === 1; 461 | } 462 | 463 | // Check to see if string passed in is a percentage 464 | function isPercentage(n) { 465 | return typeof n === "string" && n.indexOf('%') != -1; 466 | } 467 | 468 | // Force a hex value to have 2 characters 469 | function pad2(c) { 470 | return c.length == 1 ? '0' + c : '' + c; 471 | } 472 | 473 | // Replace a decimal with it's percentage value 474 | function convertToPercentage(n) { 475 | if (n <= 1) { 476 | n = (n * 100) + "%"; 477 | } 478 | 479 | return n; 480 | } 481 | 482 | var matchers = (function() { 483 | 484 | // 485 | var CSS_INTEGER = "[-\\+]?\\d+%?"; 486 | 487 | // 488 | var CSS_NUMBER = "[-\\+]?\\d*\\.\\d+%?"; 489 | 490 | // Allow positive/negative integer/number. Don't capture the either/or, just the entire outcome. 491 | var CSS_UNIT = "(?:" + CSS_NUMBER + ")|(?:" + CSS_INTEGER + ")"; 492 | 493 | // Actual matching. 494 | // Parentheses and commas are optional, but not required. 495 | // Whitespace can take the place of commas or opening paren 496 | var PERMISSIVE_MATCH3 = "[\\s|\\(]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")\\s*\\)?"; 497 | var PERMISSIVE_MATCH4 = "[\\s|\\(]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")\\s*\\)?"; 498 | 499 | return { 500 | CSS_UNIT: new RegExp(CSS_UNIT), 501 | rgb: new RegExp("rgb" + PERMISSIVE_MATCH3), 502 | rgba: new RegExp("rgba" + PERMISSIVE_MATCH4), 503 | hsl: new RegExp("hsl" + PERMISSIVE_MATCH3), 504 | hsla: new RegExp("hsla" + PERMISSIVE_MATCH4), 505 | hsv: new RegExp("hsv" + PERMISSIVE_MATCH3), 506 | hsva: new RegExp("hsva" + PERMISSIVE_MATCH4), 507 | hex3: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/, 508 | hex6: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/, 509 | hex4: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/, 510 | hex8: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/ 511 | }; 512 | })(); 513 | 514 | // isValidCSSUnit 515 | // Take in a single string / number and check to see if it looks like a CSS unit 516 | // (see matchers above for definition). 517 | function isValidCSSUnit(color) { 518 | return !!matchers.CSS_UNIT.exec(color); 519 | } 520 | 521 | // stringInputToObject 522 | // Permissive string parsing. Take in a number of formats, and output an object 523 | // based on detected format. Returns { r, g, b } or { h, s, l } or { h, s, v} 524 | function stringInputToObject(color) { 525 | 526 | color = color.replace(trimLeft, '').replace(trimRight, '').toLowerCase(); 527 | var named = false; 528 | if (names[color]) { 529 | color = names[color]; 530 | named = true; 531 | } 532 | else if (color == 'transparent') { 533 | return { r: 0, g: 0, b: 0, a: 0, format: "name" }; 534 | } 535 | 536 | // Try to match string input using regular expressions. 537 | // Keep most of the number bounding out of this function - don't worry about [0,1] or [0,100] or [0,360] 538 | // Just return an object and let the conversion functions handle that. 539 | // This way the result will be the same whether the tinycolor is initialized with string or object. 540 | var match; 541 | /* eslint-disable no-cond-assign */ 542 | if ((match = matchers.rgb.exec(color))) { 543 | return { r: match[1], g: match[2], b: match[3] }; 544 | } 545 | if ((match = matchers.rgba.exec(color))) { 546 | return { r: match[1], g: match[2], b: match[3], a: match[4] }; 547 | } 548 | if ((match = matchers.hsl.exec(color))) { 549 | return { h: match[1], s: match[2], l: match[3] }; 550 | } 551 | if ((match = matchers.hsla.exec(color))) { 552 | return { h: match[1], s: match[2], l: match[3], a: match[4] }; 553 | } 554 | if ((match = matchers.hsv.exec(color))) { 555 | return { h: match[1], s: match[2], v: match[3] }; 556 | } 557 | if ((match = matchers.hsva.exec(color))) { 558 | return { h: match[1], s: match[2], v: match[3], a: match[4] }; 559 | } 560 | if ((match = matchers.hex8.exec(color))) { 561 | return { 562 | r: parseIntFromHex(match[1]), 563 | g: parseIntFromHex(match[2]), 564 | b: parseIntFromHex(match[3]), 565 | a: convertHexToDecimal(match[4]), 566 | format: named ? "name" : "hex8" 567 | }; 568 | } 569 | if ((match = matchers.hex6.exec(color))) { 570 | return { 571 | r: parseIntFromHex(match[1]), 572 | g: parseIntFromHex(match[2]), 573 | b: parseIntFromHex(match[3]), 574 | format: named ? "name" : "hex" 575 | }; 576 | } 577 | if ((match = matchers.hex4.exec(color))) { 578 | return { 579 | r: parseIntFromHex(match[1] + '' + match[1]), 580 | g: parseIntFromHex(match[2] + '' + match[2]), 581 | b: parseIntFromHex(match[3] + '' + match[3]), 582 | a: convertHexToDecimal(match[4] + '' + match[4]), 583 | format: named ? "name" : "hex8" 584 | }; 585 | } 586 | if ((match = matchers.hex3.exec(color))) { 587 | return { 588 | r: parseIntFromHex(match[1] + '' + match[1]), 589 | g: parseIntFromHex(match[2] + '' + match[2]), 590 | b: parseIntFromHex(match[3] + '' + match[3]), 591 | format: named ? "name" : "hex" 592 | }; 593 | } 594 | /* eslint-enable no-cond-assign */ 595 | 596 | return false; 597 | } 598 | 599 | this.tinycolor = tinycolor; 600 | 601 | })()`; 602 | } 603 | // It is hacky way to make this function will be compiled preferentially by less 604 | // resolve error: `ReferenceError: colorPalette is not defined` 605 | // https://github.com/ant-design/ant-motion/issues/44 606 | .tinyColorMixin(); 607 | -------------------------------------------------------------------------------- /examples/advanced/javascript.less: -------------------------------------------------------------------------------- 1 | /* stylelint-disable declaration-bang-space-before,no-duplicate-selectors */ 2 | 3 | .echoColor() { 4 | @functions: ~`(function(colorStr) { 5 | return colorStr; 6 | } 7 | )()`; 8 | } 9 | 10 | // It is hacky way to make this function will be compiled preferentially by less 11 | .echoColor(); 12 | 13 | 14 | @import 'color/colorPalette'; 15 | 16 | // color palettes 17 | @blue-5: ~`colorPalette("@{blue-6}", 5)`; 18 | @blue-6: #1890ff; 19 | 20 | @primary-color: @blue-6; 21 | @primary-1: ~`colorPalette("@{primary-color}", 1)`; 22 | @primary-2: color(~`colorPalette("@{primary-color}", 6)`); 23 | -------------------------------------------------------------------------------- /examples/import.less: -------------------------------------------------------------------------------- 1 | .foo { 2 | background: #900; 3 | border: 1px solid @color; 4 | } 5 | 6 | @color: green; 7 | -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Styless React Example 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import Theme from './Theme'; 4 | import App from './App'; 5 | 6 | ReactDOM.render(, document.getElementById('root')); -------------------------------------------------------------------------------- /examples/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "examples", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "private": true, 7 | "scripts": { 8 | "start": "parcel --no-cache index.html" 9 | }, 10 | "devDependencies": { 11 | "parcel-bundler": "^1.12.4" 12 | }, 13 | "dependencies": { 14 | "@babel/core": "^7.6.4", 15 | "@babel/plugin-proposal-class-properties": "^7.5.5", 16 | "babel-plugin-styless": "^1.4.24", 17 | "less-vars-to-js": "^1.3.0", 18 | "react": "^16.11.0", 19 | "react-dom": "^16.11.0", 20 | "styled-components": "^4.4.1" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/variables.less: -------------------------------------------------------------------------------- 1 | @text: #333; 2 | @gray: #AAA; 3 | @highlight: palevioletred; 4 | @background: papayawhip; 5 | 6 | @light-blue: #3c8dbc; 7 | @red: #dd4b39; 8 | @green: #00a65a; 9 | @aqua: #00c0ef; 10 | @yellow: #f39c12; 11 | @navy: #001F3F; 12 | 13 | .bg-light-blue { 14 | width: 700px; 15 | background-color: @light-blue; 16 | color: contrast(@light-blue); 17 | } -------------------------------------------------------------------------------- /examples/withCheckbox.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | 3 | export default WrappedComponent => { 4 | return class extends Component { 5 | state = {checked: false}; 6 | onChange = e => { 7 | this.setState({checked: !!e.target.checked}); 8 | }; 9 | 10 | render() { 11 | const {children, ...props} = this.props; 12 | return 13 | {children} 14 | ; 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /examples/withSlider.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | 3 | export default (WrappedComponent, {min = 0, max = 100} = {}) => { 4 | return class extends Component { 5 | state = {value: 0}; 6 | onChange = e => { 7 | this.setState({value: e.target.value}); 8 | }; 9 | 10 | render() { 11 | const {children, ...props} = this.props; 12 | return 13 | {children} 14 | ; 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "babel-plugin-styless", 3 | "version": "1.0.2", 4 | "description": "Style your components declaratively with familiar less syntax", 5 | "main": "dist/index.js", 6 | "source": "src/index.js", 7 | "repository": "https://github.com/jean343/styless.git", 8 | "author": "Jean-Philippe Bergeron ", 9 | "license": "MIT", 10 | "scripts": { 11 | "test": "yarn run clean && yarn run build && jest", 12 | "watch": "jest --watch --no-cache", 13 | "clean": "jest --clearCache", 14 | "build": "babel -d dist/ src/", 15 | "prepare": "yarn run build", 16 | "prepublishOnly": "npm run build", 17 | "semantic-release": "semantic-release", 18 | "travis-deploy-once": "travis-deploy-once" 19 | }, 20 | "jest": { 21 | "testURL": "http://localhost/" 22 | }, 23 | "dependencies": { 24 | "@babel/generator": "^7.6.4", 25 | "find-babel-config": "^1.2.0", 26 | "less": "3.9.0", 27 | "tinycolor2": "^1.4.1" 28 | }, 29 | "devDependencies": { 30 | "@babel/cli": "^7.6.4", 31 | "@babel/preset-env": "^7.6.3", 32 | "@babel/preset-react": "^7.6.3", 33 | "@scoped/package": "link:./test/packages/scoped-package", 34 | "babel-plugin-styled-components": "^1.10.6", 35 | "jest": "^24.9.0", 36 | "jest-styled-components": "^6.3.3", 37 | "react": "^16.11.0", 38 | "react-dom": "^16.11.0", 39 | "react-test-renderer": "^16.11.0", 40 | "semantic-release": "^15.13.30", 41 | "styled-components": "^4.4.1", 42 | "travis-deploy-once": "^5.0.11" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/functions/boolean.js: -------------------------------------------------------------------------------- 1 | import VariableNode from '../tree/VariableNode'; 2 | import {convertNode as c} from "../tree/convert"; 3 | 4 | export const boolean = condition => { 5 | return new VariableNode(`!!(${c(condition)})`); 6 | }; 7 | 8 | const _if = (condition, trueValue, falseValue) => { 9 | const flags = { 10 | cssFragment: true 11 | }; 12 | return new VariableNode(`(${c(condition)}) ? ${c(trueValue, flags)} : ${c(falseValue, flags)}`); 13 | }; 14 | export {_if as if}; -------------------------------------------------------------------------------- /src/functions/color.js: -------------------------------------------------------------------------------- 1 | import VariableNode from "../tree/VariableNode"; 2 | import {convertNode as c} from "../tree/convert"; 3 | 4 | const parse = color => { 5 | return new VariableNode(`require("tinycolor2")(${c(color)}).toRgbString()`); 6 | }; 7 | 8 | export const rgb = (r, g, b) => { 9 | return rgba(r, g, b, 1); 10 | }; 11 | export const rgba = (r, g, b, a) => { 12 | if (g) { 13 | if (a == 1) { 14 | return new VariableNode(`require("tinycolor2")({ r: ${c(r)}, g: ${c(g)}, b: ${c(b)} }).toHexString()`); 15 | } else { 16 | return new VariableNode(`require("tinycolor2")({ r: ${c(r)}, g: ${c(g)}, b: ${c(b)}, a: ${c(a)} }).toRgbString()`); 17 | } 18 | } else { 19 | return parse(r); 20 | } 21 | }; 22 | export const hsl = (h, s, l) => { 23 | return hsla(h, s, l, 1); 24 | }; 25 | export const hsla = (h, s, l, a) => { 26 | if (s) { 27 | if (a == 1) { 28 | return new VariableNode(`require("tinycolor2")({ h: ${c(h)}, s: ${c(s)}, l: ${c(l)} }).toHexString()`); 29 | } else { 30 | return new VariableNode(`require("tinycolor2")({ h: ${c(h)}, s: ${c(s)}, l: ${c(l)}, a: ${c(a)} }).toRgbString()`); 31 | } 32 | } else { 33 | return parse(h); 34 | } 35 | }; 36 | export const hsv = (h, s, v) => { 37 | return hsva(h, s, v, 1); 38 | }; 39 | export const hsva = (h, s, v, a) => { 40 | if (s) { 41 | if (a == 1) { 42 | return new VariableNode(`require("tinycolor2")({ h: ${c(h)}, s: ${c(s)}, v: ${c(v)} }).toHexString()`); 43 | } else { 44 | return new VariableNode(`require("tinycolor2")({ h: ${c(h)}, s: ${c(s)}, v: ${c(v)}, a: ${c(a)} }).toRgbString()`); 45 | } 46 | } else { 47 | return parse(h); 48 | } 49 | }; 50 | 51 | export const hue = color => { 52 | return new VariableNode(`Math.round(require("tinycolor2")(${c(color)}).toHsl().h)`); 53 | }; 54 | export const saturation = color => { 55 | return new VariableNode(`Math.round(require("tinycolor2")(${c(color)}).toHsl().s * 100) + "%"`); 56 | }; 57 | export const lightness = color => { 58 | return new VariableNode(`Math.round(require("tinycolor2")(${c(color)}).toHsl().l * 100) + "%"`); 59 | }; 60 | 61 | export const hsvhue = color => { 62 | return new VariableNode(`Math.round(require("tinycolor2")(${c(color)}).toHsv().h)`); 63 | }; 64 | export const hsvsaturation = color => { 65 | return new VariableNode(`Math.round(require("tinycolor2")(${c(color)}).toHsv().s * 100) + "%"`); 66 | }; 67 | export const hsvvalue = color => { 68 | return new VariableNode(`Math.round(require("tinycolor2")(${c(color)}).toHsv().v * 100) + "%"`); 69 | }; 70 | 71 | export const red = color => { 72 | return new VariableNode(`require("tinycolor2")(${c(color)}).toRgb().r`); 73 | }; 74 | export const green = color => { 75 | return new VariableNode(`require("tinycolor2")(${c(color)}).toRgb().g`); 76 | }; 77 | export const blue = color => { 78 | return new VariableNode(`require("tinycolor2")(${c(color)}).toRgb().b`); 79 | }; 80 | export const alpha = color => { 81 | return new VariableNode(`Math.round(require("tinycolor2")(${c(color)}).toRgb().a * 100) / 100`); 82 | }; 83 | 84 | export const luma = color => { 85 | return new VariableNode(`require("tinycolor2")(${c(color)}).getLuminance() * 100`); 86 | }; 87 | export const luminance = color => { 88 | const compute = rgb => ((0.2126 * rgb.r / 255) + (0.7152 * rgb.g / 255) + (0.0722 * rgb.b / 255)) * rgb.a; 89 | return new VariableNode(`(${compute})(require("tinycolor2")(${c(color)}).toRgb()) * 100`); 90 | }; 91 | 92 | export const saturate = (color, amount, method) => { 93 | return new VariableNode(`require('tinycolor2')(${c(color)}).saturate(parseFloat(${c(amount)})).toRgbString()`); 94 | }; 95 | export const desaturate = (color, amount, method) => { 96 | return new VariableNode(`require('tinycolor2')(${c(color)}).desaturate(parseFloat(${c(amount)})).toRgbString()`); 97 | }; 98 | 99 | export const lighten = (color, amount, method) => { 100 | return new VariableNode(`require('tinycolor2')(${c(color)}).lighten(parseFloat(${c(amount)})).toRgbString()`); 101 | }; 102 | export const darken = (color, amount, method) => { 103 | return new VariableNode(`require('tinycolor2')(${c(color)}).darken(parseFloat(${c(amount)})).toRgbString()`); 104 | }; 105 | 106 | export const fadein = (color, amount, method) => { 107 | const compute = (color, amount, method) => { 108 | let alpha; 109 | if (typeof method !== 'undefined' && method === 'relative') { 110 | alpha = color.getAlpha() + color.getAlpha() * amount / 100; 111 | } else { 112 | alpha = color.getAlpha() + amount / 100; 113 | } 114 | color.setAlpha(Math.min(1, Math.max(0, alpha))); 115 | return color.toRgbString(); 116 | }; 117 | return new VariableNode(`(${compute})(require("tinycolor2")(${c(color)}), parseFloat(${c(amount)}), ${c(method)})`); 118 | }; 119 | export const fadeout = (color, amount, method) => { 120 | const compute = (color, amount, method) => { 121 | let alpha; 122 | if (typeof method !== 'undefined' && method === 'relative') { 123 | alpha = color.getAlpha() - color.getAlpha() * amount / 100; 124 | } else { 125 | alpha = color.getAlpha() - amount / 100; 126 | } 127 | color.setAlpha(Math.min(1, Math.max(0, alpha))); 128 | return color.toRgbString(); 129 | }; 130 | return new VariableNode(`(${compute})(require("tinycolor2")(${c(color)}), parseFloat(${c(amount)}), ${c(method)})`); 131 | }; 132 | export const fade = (color, amount) => { 133 | return new VariableNode(`require("tinycolor2")(${c(color)}).setAlpha(parseFloat(${c(amount)}) / 100).toRgbString()`); 134 | }; 135 | export const spin = (color, amount) => { 136 | return new VariableNode(`require("tinycolor2")(${c(color)}).spin(parseFloat(${c(amount)})).toRgbString()`); 137 | }; 138 | export const mix = (color1, color2, weight) => { 139 | return new VariableNode(`require("tinycolor2").mix(${c(color1)}, ${c(color2)}, parseFloat(${c(weight)})).toRgbString()`); 140 | }; 141 | export const greyscale = color => { 142 | return desaturate(color, 100); 143 | }; 144 | 145 | export const contrast = (color, dark, light, threshold) => { 146 | const compute = (t, color, dark, light, threshold) => { 147 | if (typeof light === 'undefined') { 148 | light = "#FFF"; 149 | } 150 | if (typeof dark === 'undefined') { 151 | dark = "#000"; 152 | } 153 | // Figure out which is actually light and dark: 154 | if (t(dark).getLuminance() > t(light).getLuminance()) { 155 | const t = light; 156 | light = dark; 157 | dark = t; 158 | } 159 | if (typeof threshold === 'undefined') { 160 | threshold = 0.43; 161 | } else { 162 | threshold = parseFloat(threshold) / 100; 163 | } 164 | if (t(color).getLuminance() < threshold) { 165 | return light; 166 | } else { 167 | return dark; 168 | } 169 | }; 170 | return new VariableNode(`(${compute})(require("tinycolor2"), ${c(color)}, ${c(dark)}, ${c(light)}, ${c(threshold)})`); 171 | }; 172 | 173 | export const argb = color => { 174 | return new VariableNode(`require("tinycolor2")(${c(color)}).toHex8String().replace(/#(\\w{6})(\\w{2})/, "#$2$1")`); 175 | }; 176 | // color 177 | export const tint = (color, weight) => { 178 | return mix("#ffffff", color, weight); 179 | }; 180 | export const shade = (color, weight) => { 181 | return mix("#000000", color, weight); 182 | }; -------------------------------------------------------------------------------- /src/functions/index.js: -------------------------------------------------------------------------------- 1 | export * from "./boolean"; 2 | export * from "./color"; 3 | export * from "./math"; 4 | export * from "./number"; 5 | export * from "./types"; -------------------------------------------------------------------------------- /src/functions/math.js: -------------------------------------------------------------------------------- 1 | import VariableNode from "../tree/VariableNode"; 2 | import {convertNode as c} from "../tree/convert"; 3 | 4 | const apply = (fn, n) => fn(parseFloat(n)) + ('' + n).replace(/[\d.-]*/, ""); 5 | 6 | const applyAngle = (fn, n, funit = "") => { 7 | const angle = { 8 | rad: 1, 9 | deg: Math.PI / 180, 10 | grad: Math.PI / 200, 11 | turn: Math.PI * 2, 12 | }; 13 | const unit = ('' + n).replace(/[\d.-]*/, "") || "rad"; 14 | return fn(parseFloat(n) * angle[unit]) + funit; 15 | }; 16 | 17 | export const ceil = value => { 18 | return new VariableNode(`(${apply})(Math.ceil, ${c(value)})`); 19 | }; 20 | export const floor = value => { 21 | return new VariableNode(`(${apply})(Math.floor, ${c(value)})`); 22 | }; 23 | export const sqrt = value => { 24 | return new VariableNode(`(${apply})(Math.sqrt, ${c(value)})`); 25 | }; 26 | export const abs = value => { 27 | return new VariableNode(`(${apply})(Math.abs, ${c(value)})`); 28 | }; 29 | export const tan = value => { 30 | return new VariableNode(`(${applyAngle})(Math.tan, ${c(value)})`); 31 | }; 32 | export const sin = value => { 33 | return new VariableNode(`(${applyAngle})(Math.sin, ${c(value)})`); 34 | }; 35 | export const cos = value => { 36 | return new VariableNode(`(${applyAngle})(Math.cos, ${c(value)})`); 37 | }; 38 | export const atan = value => { 39 | return new VariableNode(`(${applyAngle})(Math.atan, ${c(value)}, "rad")`); 40 | }; 41 | export const asin = value => { 42 | return new VariableNode(`(${applyAngle})(Math.asin, ${c(value)}, "rad")`); 43 | }; 44 | export const acos = value => { 45 | return new VariableNode(`(${applyAngle})(Math.acos, ${c(value)}, "rad")`); 46 | }; 47 | export const round = (value, fraction = {}) => { 48 | return new VariableNode(`(${apply})(num => num.toFixed(${fraction.value}), ${c(value)})`); 49 | }; -------------------------------------------------------------------------------- /src/functions/number.js: -------------------------------------------------------------------------------- 1 | import VariableNode from "../tree/VariableNode"; 2 | import {convertNode as c} from "../tree/convert"; 3 | 4 | export const min = (...args) => { 5 | const min = arr => arr[arr.reduce((iMax, x, i) => parseFloat(x) < parseFloat(arr[iMax]) ? i : iMax, 0)]; 6 | return new VariableNode(`(${min})([${args.map(a => c(a)).join(", ")}])`); 7 | }; 8 | export const max = (...args) => { 9 | const max = arr => arr[arr.reduce((iMax, x, i) => parseFloat(x) > parseFloat(arr[iMax]) ? i : iMax, 0)]; 10 | return new VariableNode(`(${max})([${args.map(a => c(a)).join(", ")}])`); 11 | }; 12 | export const mod = (x, y) => { 13 | const apply = (x, y) => parseFloat(x) % parseFloat(y) + ('' + x).replace(/[\d.-]*/, ""); 14 | return new VariableNode(`(${apply})(${c(x)}, ${c(y)})`); 15 | }; 16 | export const pow = (x, y) => { 17 | const apply = (x, y) => Math.pow(parseFloat(x), parseFloat(y)) + ('' + x).replace(/[\d.-]*/, ""); 18 | return new VariableNode(`(${apply})(${c(x)}, ${c(y)})`); 19 | }; 20 | export const percentage = value => { 21 | return new VariableNode(`(parseFloat(${c(value)}) * 100) + "%"`); 22 | }; -------------------------------------------------------------------------------- /src/functions/types.js: -------------------------------------------------------------------------------- 1 | import VariableNode from "../tree/VariableNode"; 2 | import {convertNode as c} from "../tree/convert"; 3 | 4 | export const iscolor = value => { 5 | return new VariableNode(`require('tinycolor2')(${c(value)}).isValid().toString()`); 6 | }; 7 | export const isnumber = value => { 8 | return new VariableNode(`(!isNaN(parseFloat(${c(value)}))).toString()`); 9 | }; 10 | export const isstring = value => { 11 | return new VariableNode(`(('' + ${c(value)})[0] === '"').toString()`); 12 | }; 13 | export const ispixel = value => { 14 | return new VariableNode(`(('' + ${c(value)}).endsWith("px")).toString()`); 15 | }; 16 | export const ispercentage = value => { 17 | return new VariableNode(`(('' + ${c(value)}).endsWith("%")).toString()`); 18 | }; 19 | export const isem = value => { 20 | return new VariableNode(`(('' + ${c(value)}).endsWith("em")).toString()`); 21 | }; -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import taggedTemplateExpressionVisitor from './visitors/taggedTemplateExpressionVisitor' 2 | 3 | export default babel => ({ 4 | visitor: { 5 | TaggedTemplateExpression(path, state) { 6 | taggedTemplateExpressionVisitor(path, state, babel); 7 | }, 8 | } 9 | }); 10 | -------------------------------------------------------------------------------- /src/tree/Condition.js: -------------------------------------------------------------------------------- 1 | import {convertNode as c} from './convert'; 2 | import VariableNode from "./VariableNode"; 3 | 4 | export default class Condition { 5 | constructor(op, l, r, i, negate) { 6 | this.op = op.trim(); 7 | this.lvalue = l; 8 | this.rvalue = r; 9 | this._index = i; 10 | this.negate = negate; 11 | } 12 | 13 | accept(visitor) { 14 | this.lvalue = visitor.visit(this.lvalue); 15 | this.rvalue = visitor.visit(this.rvalue); 16 | } 17 | 18 | eval(context) { 19 | let a = this.lvalue.eval(context); 20 | let b = this.rvalue.eval(context); 21 | a = a.value ? a.value : c(a); 22 | b = b.value ? b.value : c(b); 23 | let result; 24 | switch (this.op) { 25 | case 'and': 26 | result = `${a} && ${b}`; 27 | break; 28 | case 'or': 29 | result = `${a} || ${b}`; 30 | break; 31 | case '=': 32 | if (b === "true") { 33 | result = `!!${a}`; 34 | } else { 35 | result = `${a} == ${b}`; 36 | } 37 | break; 38 | default: 39 | result = `${a} ${this.op} ${b}`; 40 | break; 41 | } 42 | 43 | if (this.negate) { 44 | return new VariableNode(`!${result}`); 45 | } else { 46 | return new VariableNode(result); 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /src/tree/Dimension.js: -------------------------------------------------------------------------------- 1 | import dimension from 'less/lib/less/tree/dimension'; 2 | import VariableNode from "./VariableNode"; 3 | import operate from "./operate"; 4 | 5 | export default class Dimension extends dimension { 6 | constructor(value, unit) { 7 | super(value, unit); 8 | } 9 | 10 | operate(context, op, other) { 11 | return new VariableNode(operate(context, this, op, other)); 12 | } 13 | } -------------------------------------------------------------------------------- /src/tree/JavaScript.js: -------------------------------------------------------------------------------- 1 | import JavaScriptLess from 'less/lib/less/tree/javascript'; 2 | 3 | export default class JavaScript extends JavaScriptLess { 4 | jsify(obj) { 5 | let result = super.jsify(obj); 6 | if (/^\[props\[\"/.test(result)) { 7 | result = result.replace(/"/gm, "\\\""); 8 | } 9 | return result; 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/tree/Negative.js: -------------------------------------------------------------------------------- 1 | import node from 'less/lib/less/tree/node'; 2 | import convert from './convert'; 3 | import VariableNode from './VariableNode'; 4 | 5 | export default class Negative extends node { 6 | constructor(node) { 7 | super(node); 8 | this.value = node; 9 | } 10 | 11 | eval(context) { 12 | return new VariableNode(`"-" + (${convert(this.value.name)})`); 13 | } 14 | } -------------------------------------------------------------------------------- /src/tree/Variable.js: -------------------------------------------------------------------------------- 1 | import variable from 'less/lib/less/tree/variable'; 2 | import convert, {convertNode as c} from './convert'; 3 | import VariableNode from './VariableNode'; 4 | 5 | export default class Variable extends variable { 6 | constructor(name, index, currentFileInfo) { 7 | super(name, index, currentFileInfo); 8 | this.name = name; 9 | this._index = index; 10 | this._fileInfo = currentFileInfo; 11 | } 12 | 13 | eval(context) { 14 | let localVariable; 15 | try { 16 | localVariable = super.eval(context); 17 | } finally { 18 | return new VariableNode(convert(this.name, c(localVariable, {cssFragment: true}))); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /src/tree/VariableNode.js: -------------------------------------------------------------------------------- 1 | import Node from 'less/lib/less/tree/node'; 2 | import operate from "./operate"; 3 | 4 | export default class VariableNode extends Node { 5 | constructor(value, longValue) { 6 | super(); 7 | this.value = value; 8 | } 9 | 10 | genCSS(context, output) { 11 | if (context && context.cssFragment) { 12 | output.add(`\${${this.value}}`); 13 | } else { 14 | output.add(`\${props => ${this.value}}`); 15 | } 16 | } 17 | 18 | toCSS(context, doNotCompress) { 19 | return this.value; 20 | } 21 | 22 | operate(context, op, other) { 23 | return new VariableNode(operate(context, this, op, other)); 24 | } 25 | } -------------------------------------------------------------------------------- /src/tree/convert.js: -------------------------------------------------------------------------------- 1 | import VariableNode from "./VariableNode"; 2 | 3 | const varRgx = /^[@$]/; 4 | export default (val, defaultValue) => { 5 | if (val === undefined) return val; 6 | if (!('' + val).startsWith("@")) { 7 | return `"${val}"`; 8 | } 9 | val = val.replace(varRgx, ''); 10 | const parts = [ 11 | `props["${val}"]`, 12 | `(props.theme || {})["${val}"]`, 13 | defaultValue, 14 | ]; 15 | return `[${parts.filter(v => !!v).join(",")}].filter(v => v !== void 0)[0]`; 16 | }; 17 | 18 | export const convertNode = (v, {cssFragment} = {}) => { 19 | if (!v) 20 | return undefined; 21 | let value = v.toCSS ? v.toCSS({cssFragment}) : v.toString(); 22 | if (v instanceof VariableNode) { 23 | return value; 24 | } 25 | // Deals with "strings" 26 | if (value.startsWith('`')) { 27 | return value; 28 | } 29 | 30 | return `\`${value}\``; 31 | }; -------------------------------------------------------------------------------- /src/tree/operate.js: -------------------------------------------------------------------------------- 1 | export default (context, self, op, other) => { 2 | let unit = (self.unit && self.unit.toString()) || (other.unit && other.unit.toString()); 3 | if (unit) { 4 | unit = `"${unit}"`; 5 | } else { 6 | unit = `(('' + ${self.value}).replace(/[\\d.-]*/, "") || ('' + ${other.value}).replace(/[\\d.-]*/, ""))`; 7 | } 8 | return `parseFloat(${self.value}) ${op} parseFloat(${other.value}) + ${unit}`; 9 | } -------------------------------------------------------------------------------- /src/utils/detectors.js: -------------------------------------------------------------------------------- 1 | const VALID_TOP_LEVEL_IMPORT_PATHS = [ 2 | 'styled-components', 3 | 'styled-components/no-tags', 4 | 'styled-components/native', 5 | 'styled-components/primitives', 6 | ]; 7 | 8 | export const isValidTopLevelImport = x => 9 | VALID_TOP_LEVEL_IMPORT_PATHS.includes(x); 10 | 11 | const localNameCache = {}; 12 | 13 | export const importLocalName = (name, state, bypassCache = false) => { 14 | const cacheKey = name + state.file.opts.filename 15 | 16 | if (!bypassCache && cacheKey in localNameCache) { 17 | return localNameCache[cacheKey] 18 | } 19 | 20 | let localName = state.styledRequired 21 | ? name === 'default' 22 | ? 'styled' 23 | : name 24 | : false 25 | 26 | state.file.path.traverse({ 27 | ImportDeclaration: { 28 | exit(path) { 29 | const {node} = path; 30 | 31 | if (isValidTopLevelImport(node.source.value)) { 32 | for (const specifier of path.get('specifiers')) { 33 | if (specifier.isImportDefaultSpecifier()) { 34 | localName = specifier.node.local.name 35 | } 36 | 37 | if ( 38 | specifier.isImportSpecifier() && 39 | specifier.node.imported.name === name 40 | ) { 41 | localName = specifier.node.local.name 42 | } 43 | 44 | if (specifier.isImportNamespaceSpecifier()) { 45 | localName = specifier.node.local.name 46 | } 47 | } 48 | } 49 | }, 50 | }, 51 | }); 52 | localNameCache[cacheKey] = localName 53 | return localName 54 | }; 55 | 56 | export const isStyled = t => (tag, state) => { 57 | /* Matches the extend blocks such as 58 | const Block = Div.extend` 59 | color: @color 60 | ` 61 | */ 62 | if (tag.property && tag.property.name === "extend") { 63 | return true; 64 | } 65 | 66 | if ( 67 | t.isCallExpression(tag) && 68 | t.isMemberExpression(tag.callee) && 69 | tag.callee.property.name !== 'default' /** ignore default for #93 below */ 70 | ) { 71 | // styled.something() 72 | return isStyled(t)(tag.callee.object, state) 73 | } else { 74 | return ( 75 | (t.isMemberExpression(tag) && 76 | tag.object.name === importLocalName('default', state)) || 77 | (t.isCallExpression(tag) && 78 | tag.callee.name === importLocalName('default', state)) || 79 | /** 80 | * #93 Support require() 81 | * styled-components might be imported using a require() 82 | * call and assigned to a variable of any name. 83 | * - styled.default.div`` 84 | * - styled.default.something() 85 | */ 86 | (state.styledRequired && 87 | t.isMemberExpression(tag) && 88 | t.isMemberExpression(tag.object) && 89 | tag.object.property.name === 'default' && 90 | tag.object.object.name === state.styledRequired) || 91 | (state.styledRequired && 92 | t.isCallExpression(tag) && 93 | t.isMemberExpression(tag.callee) && 94 | tag.callee.property.name === 'default' && 95 | tag.callee.object.name === state.styledRequired) 96 | ) 97 | } 98 | }; 99 | 100 | export const isCSSHelper = t => (tag, state) => t.isIdentifier(tag) && tag.name === importLocalName('css', state); 101 | 102 | export const isCreateGlobalStyleHelper = t => (tag, state) => t.isIdentifier(tag) && tag.name === importLocalName('createGlobalStyle', state); 103 | 104 | export const isInjectGlobalHelper = t => (tag, state) => t.isIdentifier(tag) && tag.name === importLocalName('injectGlobal', state); 105 | 106 | export const isKeyframesHelper = t => (tag, state) => t.isIdentifier(tag) && tag.name === importLocalName('keyframes', state); 107 | 108 | export const isHelper = t => (tag, state) => isCSSHelper(t)(tag, state) || isKeyframesHelper(t)(tag, state); 109 | 110 | export const isPureHelper = t => (tag, state) => isCSSHelper(t)(tag, state) || isKeyframesHelper(t)(tag, state) || isCreateGlobalStyleHelper(t)(tag, state); -------------------------------------------------------------------------------- /src/visitors/taggedTemplateExpressionVisitor.js: -------------------------------------------------------------------------------- 1 | import {isStyled, isHelper, isPureHelper} from "../utils/detectors"; 2 | import transpileLess from "./transpileLess"; 3 | import generate from '@babel/generator'; 4 | 5 | const regex = /`([\s\S]*)`/; 6 | 7 | export default (path, state, {types: t}) => { 8 | if (!(isStyled(t)(path.node.tag, state) || isPureHelper(t)(path.node.tag || path.node.callee, state))) { 9 | return; 10 | } 11 | 12 | // Find the TemplateLiteral in the TaggedTemplateExpression 13 | path.traverse({ 14 | TemplateLiteral(p) { 15 | if (p.isClean) return; 16 | p.stop(); // Only traverse the first TemplateLiteral of TaggedTemplateExpression 17 | 18 | let rawSource = p.getSource(); 19 | if (!rawSource) { 20 | const {code} = generate({ 21 | type: 'Program', 22 | body: [path.node] 23 | }); 24 | rawSource = code; 25 | } 26 | 27 | const [foo, source] = regex.exec(rawSource); 28 | if (!source) return; 29 | p.isClean = true; 30 | 31 | try { 32 | const raw = transpileLess(source, state.file.opts.filename, state.opts); 33 | if (source !== raw) { 34 | p.replaceWithSourceString('`' + raw + '`'); 35 | } 36 | } catch (e) { 37 | console.error("Error converting the less syntax for the file:", state.file.opts.filename, rawSource, e); 38 | } 39 | }, 40 | }); 41 | } -------------------------------------------------------------------------------- /src/visitors/transpileLess.js: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import findBabelConfig from 'find-babel-config'; 3 | import Less from "less/lib/less/"; 4 | import FileManager from "less/lib/less-node/file-manager"; 5 | import _utils from "less/lib/less/utils"; 6 | import Variable from "../tree/Variable"; 7 | import Condition from "../tree/Condition"; 8 | import Negative from "../tree/Negative"; 9 | import Dimension from "../tree/Dimension"; 10 | import JavaScript from "../tree/JavaScript"; 11 | import * as functions from '../functions'; 12 | import {genCSS, anonymousEval, nodeParse} from "./utils"; 13 | 14 | const transpile = (less, source, filename, opts = {}) => { 15 | let banner = ""; 16 | if (opts.import) { 17 | banner += `@import (reference) "${opts.import}";`; 18 | } 19 | const paths = [path.dirname(filename)]; 20 | 21 | // Add the last semi-colon if needed 22 | source = source.trim(); 23 | if (!source.endsWith(";")) { 24 | source += ";"; 25 | } 26 | 27 | switch (opts.cwd) { 28 | case "babelrc": 29 | paths.push(path.dirname(findBabelConfig.sync(filename).file)); 30 | break; 31 | default: 32 | case "cwd": 33 | paths.push(process.cwd()); 34 | break; 35 | } 36 | 37 | const parseOpts = Object.assign( 38 | {}, 39 | { 40 | math: 0, 41 | paths, 42 | banner, 43 | syncImport: true, 44 | }, 45 | opts.lessOptions 46 | ); 47 | 48 | let root, imports, options; 49 | less.parse(source, parseOpts, (e, _root, _imports, _options) => { 50 | root = _root; 51 | imports = _imports; 52 | options = _options; 53 | }); 54 | 55 | if (!root) { 56 | console.error("Failed to parse", source); 57 | return source; 58 | } 59 | // Disables the validation: Properties must be inside selector blocks. They cannot be in the root 60 | root.firstRoot = false; 61 | const parseTree = new less.ParseTree(root, imports); 62 | const {css} = parseTree.toCSS(options); 63 | return css; 64 | }; 65 | 66 | export default (source, filename, opts) => { 67 | const fileManager = new FileManager(); 68 | // Adding support for scoped packages by removing the leading '~' 69 | fileManager.loadFile = function (filename, currentDirectory, options, environment, callback) { 70 | return FileManager.prototype.loadFile.call(this, filename.replace(/^~/, ""), currentDirectory, options, environment, callback); 71 | }; 72 | 73 | const less = new Less(undefined, [fileManager]); 74 | const oldGenCSS = less.tree.Expression.prototype.genCSS; 75 | const oldEval = less.tree.Anonymous.prototype.eval; 76 | const oldTree = Object.assign({}, less.tree); 77 | const oldFunctionRegistry = Object.assign({}, less.functions.functionRegistry._data); 78 | const oldJoinSelector = less.tree.Ruleset.prototype.joinSelector; 79 | try { 80 | Object.defineProperty(less.tree.Node.prototype, 'parse', nodeParse(less)); 81 | less.tree.Variable = Variable; 82 | less.tree.Condition = Condition; 83 | less.tree.Negative = Negative; 84 | less.tree.Dimension = Dimension; 85 | less.tree.JavaScript = JavaScript; 86 | less.tree.Expression.prototype.genCSS = genCSS(less); 87 | less.tree.Anonymous.prototype.eval = anonymousEval(less); 88 | 89 | // Changes the function joinSelector to allow & selectors in the root element. Useful for overriding styles with higher specificity. 90 | const {Paren, Selector, Element} = less.tree; 91 | const joinSelector = less.tree.Ruleset.prototype.joinSelector.toString().replace("if (el.value !== '&') {", "if (el.value !== '&' || context.length === 0) {"); 92 | let utils = _utils; 93 | eval(`less.tree.Ruleset.prototype.joinSelector = ${joinSelector}`); 94 | 95 | less.functions.functionRegistry.addMultiple(functions); 96 | less.PluginLoader = class PluginLoader { 97 | }; 98 | 99 | return transpile(less, source, filename, opts); 100 | } finally { 101 | delete less.tree.Node.prototype.parse; 102 | Object.assign(less.tree, oldTree); 103 | less.tree.Expression.prototype.genCSS = oldGenCSS; 104 | less.tree.Anonymous.prototype.eval = oldEval; 105 | less.tree.Ruleset.prototype.joinSelector = oldJoinSelector; 106 | less.functions.functionRegistry._data = oldFunctionRegistry; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/visitors/utils.js: -------------------------------------------------------------------------------- 1 | import VariableNode from "../tree/VariableNode"; 2 | 3 | const consumeBrackets = parserInput => { 4 | let inComment = false; 5 | let blockDepth = 0; 6 | const blockStack = []; 7 | const input = parserInput.getInput(); 8 | const length = input.length; 9 | const lastPos = parserInput.i; 10 | let i = parserInput.i; 11 | let prevChar, nextChar; 12 | 13 | do { 14 | prevChar, nextChar = input.charAt(i); 15 | if (blockDepth === 0 && prevChar === "}") { 16 | return input.substr(lastPos, i - lastPos); 17 | } else { 18 | if (inComment) { 19 | if (nextChar === '*' && 20 | input.charAt(i + 1) === '/') { 21 | i++; 22 | blockDepth--; 23 | inComment = false; 24 | } 25 | i++; 26 | continue; 27 | } 28 | switch (nextChar) { 29 | case '/': 30 | if (input.charAt(i + 1) === '*') { 31 | i++; 32 | inComment = true; 33 | blockDepth++; 34 | } 35 | break; 36 | case "'": 37 | case '"': 38 | let quote = parserInput.$quoted(i); 39 | if (quote) { 40 | i += quote[1].length - 1; 41 | } 42 | break; 43 | case '{': 44 | blockStack.push('}'); 45 | blockDepth++; 46 | break; 47 | case '(': 48 | blockStack.push(')'); 49 | blockDepth++; 50 | break; 51 | case '[': 52 | blockStack.push(']'); 53 | blockDepth++; 54 | break; 55 | case '}': 56 | case ')': 57 | case ']': 58 | var expected = blockStack.pop(); 59 | if (nextChar === expected) { 60 | blockDepth--; 61 | } else { 62 | return expected; 63 | } 64 | } 65 | i++; 66 | } 67 | prevChar = nextChar; 68 | } while (i <= length); 69 | }; 70 | 71 | export const genCSS = less => function (context, output) { 72 | const cssFragment = context && context.cssFragment && this.value.some(v => v instanceof VariableNode); 73 | if (cssFragment) output.add('`'); 74 | for (let i = 0; i < this.value.length; i++) { 75 | const value = this.value[i]; 76 | if (cssFragment && value instanceof VariableNode) { 77 | output.add(`\${${value.toCSS()}}`); 78 | } else { 79 | value.genCSS(context, output); 80 | } 81 | if (!(this.noSpacing || value.noSpacing) && i + 1 < this.value.length) { 82 | output.add(' '); 83 | } 84 | } 85 | if (cssFragment) output.add('`'); 86 | }; 87 | export const anonymousEval = less => function () { 88 | const anonymous = new less.tree.Anonymous(this.value, this._index, this._fileInfo, this.mapLines, this.rulesetLike, this.visibilityInfo()); 89 | anonymous.noSpacing = this.noSpacing; 90 | return anonymous; 91 | }; 92 | 93 | let lastSelf; 94 | export const nodeParse = less => ({ 95 | get: () => lastSelf, 96 | set: self => { 97 | lastSelf = self; 98 | const $re = (parserInput, tok) => { 99 | parserInput.save(); 100 | const ret = parserInput.$re(tok); 101 | parserInput.restore(); 102 | return ret; 103 | }; 104 | const parseJS = (orig, treeConstructor, isDeclaration) => { 105 | const val = orig(); 106 | if (val) 107 | return val; 108 | 109 | let name; 110 | self.parserInput.save(); 111 | if ($re(self.parserInput, /^\.?\$/) && (name = consumeBrackets(self.parserInput))) { 112 | // Makes the assumption that all nested code blocks have css in them, or ends with a ;. 113 | if (isDeclaration && !name.includes("css")) { 114 | self.parserInput.save(); 115 | self.parserInput.$str(name); 116 | if (self.parserInput.currentChar() !== ';') { 117 | self.parserInput.restore(); 118 | return; 119 | } 120 | } 121 | 122 | self.parserInput.$str(name); 123 | const anonymous = new (less.tree.Anonymous)(name, self.parserInput.i, self.fileInfo); 124 | anonymous.noSpacing = self.parserInput.prevChar() !== " "; 125 | if (treeConstructor) { 126 | return treeConstructor(anonymous); 127 | } else { 128 | return anonymous; 129 | } 130 | } 131 | self.parserInput.restore(); 132 | }; 133 | 134 | self.parsers.declaration = parseJS.bind(null, self.parsers.declaration.bind(self.parsers), undefined, true); 135 | self.parsers.entities.variable = parseJS.bind(null, self.parsers.entities.variable.bind(self.parsers)); 136 | self.parsers.element = parseJS.bind(null, self.parsers.element.bind(self.parsers), anonymous => { 137 | const c = self.parsers.combinator(); 138 | return new (less.tree.Element)(c, anonymous, true, self.parserInput.i, self.fileInfo); 139 | }); 140 | }, 141 | configurable: true, 142 | }); -------------------------------------------------------------------------------- /test/Browser/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Styless Browser Test 6 | 7 | 8 | 9 | 10 | 11 | Check the console 12 | 13 | 14 | -------------------------------------------------------------------------------- /test/Browser/index.js: -------------------------------------------------------------------------------- 1 | import Less from "less/lib/less/"; 2 | import * as functions from '../../src/functions'; 3 | import Variable from "../../src/tree/Variable"; 4 | import Condition from "../../src/tree/Condition"; 5 | import Negative from "../../src/tree/Negative"; 6 | import Dimension from "../../src/tree/Dimension"; 7 | import {anonymousEval, genCSS, nodeParse} from "../../src/visitors/utils"; 8 | import JavaScript from "../../src/tree/JavaScript"; 9 | 10 | const less = new Less(); 11 | 12 | let source = ` 13 | box-shadow: if(not @disabled, inset 0 0 0 30px rgba(255, 255, 255, 0.5)); 14 | `; 15 | 16 | const transpile = async (less, source, filename, opts = {}) => { 17 | const parseOpts = Object.assign({}, {math: 0},); 18 | 19 | const {root, imports, options} = await new Promise(resolve => { 20 | less.parse(source, parseOpts, (e, root, imports, options) => resolve({root, imports, options})); 21 | }); 22 | 23 | if (!root) { 24 | console.error("Failed to parse", source); 25 | return source; 26 | } 27 | // Disables the validation: Properties must be inside selector blocks. They cannot be in the root 28 | root.firstRoot = false; 29 | const parseTree = new less.ParseTree(root, imports); 30 | const {css} = parseTree.toCSS(options); 31 | return css; 32 | }; 33 | 34 | (async () => { 35 | Object.defineProperty(less.tree.Node.prototype, 'parse', nodeParse(less)); 36 | less.tree.Variable = Variable; 37 | less.tree.Condition = Condition; 38 | less.tree.Negative = Negative; 39 | less.tree.Dimension = Dimension; 40 | less.tree.JavaScript = JavaScript; 41 | less.tree.Expression.prototype.genCSS = genCSS(less); 42 | less.tree.Anonymous.prototype.eval = anonymousEval(less); 43 | 44 | // Changes the function joinSelector to allow & selectors in the root element. Useful for overriding styles with higher specificity. 45 | const {Paren, Selector, Element} = less.tree; 46 | const joinSelector = less.tree.Ruleset.prototype.joinSelector.toString().replace("if (el.value !== '&') {", "if (el.value !== '&' || context.length === 0) {"); 47 | eval(`less.tree.Ruleset.prototype.joinSelector = ${joinSelector}`); 48 | 49 | less.functions.functionRegistry.addMultiple(functions); 50 | less.PluginLoader = class PluginLoader { 51 | }; 52 | 53 | const transpiled = await transpile(less, source, "filename", {}); 54 | console.info("transpiled", transpiled); 55 | })(); -------------------------------------------------------------------------------- /test/Browser/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "private": true, 7 | "browserslist": "last 2 chrome versions", 8 | "scripts": { 9 | "start": "parcel --no-cache index.html" 10 | }, 11 | "devDependencies": { 12 | "@babel/core": "^7.1.6", 13 | "parcel-bundler": "^1.10.3" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test/Component/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | "@babel/preset-react" 5 | ], 6 | "plugins": [["../../", { "lessOptions": { "javascriptEnabled": true } }]] 7 | } 8 | -------------------------------------------------------------------------------- /test/Component/BabelPluginStyledComponents/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | "@babel/preset-react" 5 | ], 6 | "plugins": [ 7 | [ 8 | "../../../", 9 | { 10 | "import": "../base.less" 11 | } 12 | ], 13 | "babel-plugin-styled-components" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /test/Component/BabelPluginStyledComponents/StyledComponent.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import renderer from 'react-test-renderer'; 3 | import styled, {css} from "styled-components"; 4 | import 'jest-styled-components'; 5 | 6 | test('Basic css prop', () => { 7 | // https://www.styled-components.com/docs/api#css-prop 8 | expect(renderer.create(
).toJSON()).toMatchSnapshot(); 14 | expect(renderer.create( 35 | Tomato Button 36 |
).toJSON()).toMatchSnapshot(); 37 | }); -------------------------------------------------------------------------------- /test/Component/Functional.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import renderer from 'react-test-renderer'; 3 | import styled from 'styled-components'; 4 | import 'jest-styled-components'; 5 | 6 | test('Darkens the primary color 20%', () => { 7 | const Div = styled.div` 8 | color: darken(@primary, 20%); 9 | `; 10 | const tree = renderer.create(
).toJSON(); 11 | expect(tree).toMatchSnapshot(); 12 | }); 13 | 14 | test('Darkens the colors in linear-gradient', () => { 15 | const Div = styled.div` 16 | background: linear-gradient(lighten(@start, 20%) 0%, darken(@end, 20%) 100%); 17 | `; 18 | const tree = renderer.create(
).toJSON(); 19 | expect(tree).toMatchSnapshot(); 20 | }); -------------------------------------------------------------------------------- /test/Component/ImportFromScopedNPM.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import renderer from 'react-test-renderer'; 3 | import styled from "styled-components"; 4 | import 'jest-styled-components'; 5 | 6 | test('Support import from scoped npm', () => { 7 | const Div = styled.div` 8 | @import "./import-scoped"; 9 | color: @foo; 10 | `; 11 | expect(renderer.create(
).toJSON()).toMatchSnapshot(); 12 | }); -------------------------------------------------------------------------------- /test/Component/Imports.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import renderer from 'react-test-renderer'; 3 | import styled, {css} from "styled-components"; 4 | import 'jest-styled-components'; 5 | 6 | test('Support import', () => { 7 | const Div = styled.div` 8 | @import "import.less"; 9 | color: @color; 10 | .foo; 11 | `; 12 | expect(renderer.create(
).toJSON()).toMatchSnapshot(); 13 | expect(renderer.create(
).toJSON()).toMatchSnapshot(); 14 | }); 15 | 16 | test('Support import reference', () => { 17 | const Div = styled.div` 18 | @import (reference) "import.less"; 19 | color: @color; 20 | .foo; 21 | `; 22 | expect(renderer.create(
).toJSON()).toMatchSnapshot(); 23 | expect(renderer.create(
).toJSON()).toMatchSnapshot(); 24 | }); -------------------------------------------------------------------------------- /test/Component/Javascript.test.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import renderer from "react-test-renderer"; 3 | import styled from "styled-components"; 4 | import "jest-styled-components"; 5 | 6 | test("Support javascript", () => { 7 | const Div = styled.div` 8 | @import "javascript.less"; 9 | @color: green; 10 | color: @echoColor(@color); 11 | background: @primary-1; 12 | border: 1px solid @primary-2; 13 | `; 14 | expect(renderer.create(
).toJSON()).toMatchSnapshot(); 15 | expect(renderer.create(
).toJSON()).toMatchSnapshot(); 16 | }); 17 | -------------------------------------------------------------------------------- /test/Component/Less.test.js: -------------------------------------------------------------------------------- 1 | import less from "less"; 2 | 3 | test('Less should still compile normally', () => { 4 | const transpileLess = require("../../src/visitors/transpileLess").default; 5 | transpileLess("", ""); 6 | 7 | const input = ` 8 | @color: red; 9 | div { 10 | color: @color; 11 | darken: darken(@color, 20%); 12 | } 13 | `; 14 | less.render(input).then(({css}) => { 15 | expect(css).toMatchSnapshot(); 16 | }); 17 | }); -------------------------------------------------------------------------------- /test/Component/List.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import renderer from 'react-test-renderer'; 3 | import styled from 'styled-components'; 4 | import 'jest-styled-components'; 5 | 6 | test.skip('Returns the number of elements in a value list', () => { 7 | const Div = styled.div` 8 | @list: "banana", "tomato", "potato", "peach"; 9 | n: length(@list); 10 | n2: length(1px solid #0080ff); 11 | `; 12 | expect(renderer.create(
).toJSON()).toMatchSnapshot(); 13 | }); 14 | 15 | test.skip('Returns the value at a specified position in a list.', () => { 16 | const Div = styled.div` 17 | @list: apple, pear, coconut, orange; 18 | value: extract(@list, 3); 19 | `; 20 | expect(renderer.create(
).toJSON()).toMatchSnapshot(); 21 | }); -------------------------------------------------------------------------------- /test/Component/Math.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import renderer from 'react-test-renderer'; 3 | import styled from 'styled-components'; 4 | import 'jest-styled-components'; 5 | 6 | test('Does some math.', () => { 7 | const Div = styled.div` 8 | width: 100px + 100; 9 | width: 100 + 100px; 10 | width1: @a + @b; 11 | width1: @a - @b; 12 | width1: @a * @b; 13 | width1: @a / @b; 14 | 15 | width2: @b + @a; 16 | width2: @b - @a; 17 | width2: @b * @a; 18 | width2: @b / @a; 19 | 20 | width3: @a + @a + @a; 21 | width3: (@a + @a + @a) / 3; 22 | `; 23 | expect(renderer.create(
).toJSON()).toMatchSnapshot(); 24 | }); 25 | 26 | test('Rounds up to the next highest integer.', () => { 27 | const Div = styled.div` 28 | ceil: ceil(2.4); 29 | ceil: ceil(@value); 30 | ceil: ceil(2.4px); 31 | ceil: ceil(@value2); 32 | `; 33 | expect(renderer.create(
).toJSON()).toMatchSnapshot(); 34 | }); 35 | 36 | test('Rounds down to the next lowest integer.', () => { 37 | const Div = styled.div` 38 | floor: floor(2.6); 39 | floor: floor(@value); 40 | floor: floor(2.6em); 41 | floor: floor(@value2); 42 | `; 43 | expect(renderer.create(
).toJSON()).toMatchSnapshot(); 44 | }); 45 | 46 | test('Converts a floating point number into a percentage string.', () => { 47 | const Div = styled.div` 48 | p: percentage(0.5); 49 | p: percentage(0.5em); 50 | p: percentage(@value); 51 | `; 52 | expect(renderer.create(
).toJSON()).toMatchSnapshot(); 53 | }); 54 | 55 | test('Applies rounding.', () => { 56 | const Div = styled.div` 57 | round: round(1.67); 58 | round: round(@value); 59 | round: round(1.67px); 60 | round: round(@value2); 61 | `; 62 | expect(renderer.create(
).toJSON()).toMatchSnapshot(); 63 | }); 64 | 65 | test('Calculates square root of a number. Keeps units as they are.', () => { 66 | const Div = styled.div` 67 | sqrt: sqrt(25cm); 68 | sqrt: sqrt(@value); 69 | `; 70 | expect(renderer.create(
).toJSON()).toMatchSnapshot(); 71 | }); 72 | 73 | test('Calculates absolute value of a number. Keeps units as they are.', () => { 74 | const Div = styled.div` 75 | abs: abs(25cm); 76 | abs: abs(-18.6%); 77 | abs: abs(@value); 78 | abs: abs(@value2); 79 | `; 80 | expect(renderer.create(
).toJSON()).toMatchSnapshot(); 81 | }); 82 | 83 | test('Calculates sine function.', () => { 84 | const Div = styled.div` 85 | sin: sin(@value); // sine of 1 radian 86 | deg: sin(1deg); // sine of 1 degree 87 | grad: sin(1grad); // sine of 1 gradian 88 | turn: sin(0.2turn); // sine of 1 gradian 89 | `; 90 | expect(renderer.create(
).toJSON()).toMatchSnapshot(); 91 | }); 92 | 93 | test('Calculates arcsine (inverse of sine) function.', () => { 94 | const Div = styled.div` 95 | asin: asin(0.5); 96 | asin: asin(@zero); 97 | asin: asin(2); 98 | `; 99 | expect(renderer.create(
).toJSON()).toMatchSnapshot(); 100 | }); 101 | 102 | test('Calculates cosine function.', () => { 103 | const Div = styled.div` 104 | sin: cos(@value); // cos of 1 radian 105 | deg: cos(1deg); // cos of 1 degree 106 | grad: cos(1grad); // cos of 1 gradian 107 | turn: cos(0.2turn); // cos of 1 gradian 108 | `; 109 | expect(renderer.create(
).toJSON()).toMatchSnapshot(); 110 | }); 111 | 112 | test('Calculates arccosine (inverse of cosine) function.', () => { 113 | const Div = styled.div` 114 | acos: acos(0.5); 115 | acos: acos(@one); 116 | acos: acos(2); 117 | `; 118 | expect(renderer.create(
).toJSON()).toMatchSnapshot(); 119 | }); 120 | 121 | test('Calculates tangent function.', () => { 122 | const Div = styled.div` 123 | tan: tan(1); // tangent of 1 radian 124 | tan: tan(1deg); // tangent of 1 degree 125 | tan: tan(1grad); // tangent of 1 gradian 126 | `; 127 | expect(renderer.create(
).toJSON()).toMatchSnapshot(); 128 | }); 129 | 130 | test('Calculates arctangent (inverse of tangent) function.', () => { 131 | const Div = styled.div` 132 | atan: atan(-1.5574077246549023); 133 | atan: atan(@zero); 134 | atan: round(atan(22), 6); // arctangent of 22 rounded to 6 decimal places 135 | `; 136 | expect(renderer.create(
).toJSON()).toMatchSnapshot(); 137 | }); 138 | 139 | test('PI', () => { 140 | const Div = styled.div` 141 | pi: pi(); 142 | `; 143 | expect(renderer.create(
).toJSON()).toMatchSnapshot(); 144 | }); 145 | 146 | test('Returns the value of the first argument raised to the power of the second argument.', () => { 147 | const Div = styled.div` 148 | pow: pow(0cm, 0px); 149 | pow: pow(25, -1); 150 | pow: pow(25, 0.5); 151 | pow: pow(-25, 0.5); 152 | pow: pow(-25%, -0.5); 153 | pow: pow(@x, @y); 154 | `; 155 | expect(renderer.create(
).toJSON()).toMatchSnapshot(); 156 | }); 157 | 158 | test('Returns the value of the first argument modulus second argument.', () => { 159 | const Div = styled.div` 160 | mod: mod(0cm, 0px); 161 | mod: mod(11cm, 6px); 162 | mod: mod(-26%, -5); 163 | mod: mod(@x, @y); 164 | `; 165 | expect(renderer.create(
).toJSON()).toMatchSnapshot(); 166 | }); 167 | 168 | test('Returns the lowest of one or more values.', () => { 169 | const Div = styled.div` 170 | min: min(5, 10); 171 | min: min(3px, 42px, 2px, 16px); 172 | min: min(@a, @b, @c); 173 | `; 174 | expect(renderer.create(
).toJSON()).toMatchSnapshot(); 175 | }); 176 | 177 | test('Returns the highest of one or more values.', () => { 178 | const Div = styled.div` 179 | max: max(5, 10); 180 | max: max(3%, 42%, 1%, 16%); 181 | max: max(@a, @b, @c); 182 | `; 183 | expect(renderer.create(
).toJSON()).toMatchSnapshot(); 184 | }); 185 | 186 | test('Does some conditional math.', () => { 187 | const Div = styled.div` 188 | a: if(boolean(true), @a + @b, @a - @b); 189 | b: if(boolean(false), @a + @b, @a - @b); 190 | `; 191 | expect(renderer.create(
).toJSON()).toMatchSnapshot(); 192 | }); -------------------------------------------------------------------------------- /test/Component/Mixins.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import renderer from 'react-test-renderer'; 3 | import styled, {css} from "styled-components"; 4 | import 'jest-styled-components'; 5 | 6 | test('Support inline mixins', () => { 7 | const Div = styled.div` 8 | .my-mixin { 9 | color: @color; 10 | } 11 | .my-other-mixin() { 12 | background: @background; 13 | } 14 | .class { 15 | .my-mixin(); 16 | .my-other-mixin(); 17 | } 18 | `; 19 | expect(renderer.create(
).toJSON()).toMatchSnapshot(); 20 | }); 21 | 22 | test.skip('Test mixin guards', () => { 23 | const Div = styled.div` 24 | .mixin(@a) when (lightness(@a) >= 50%) { 25 | background-color: black; 26 | } 27 | .mixin(@a) when (lightness(@a) < 50%) { 28 | background-color: white; 29 | } 30 | .mixin(@a) { 31 | color: @a; 32 | } 33 | .class1 { .mixin(#ddd) } 34 | .class2 { .mixin(#555) } 35 | `; 36 | expect(renderer.create(
).toJSON()).toMatchSnapshot(); 37 | }); 38 | 39 | // test.skip('Test Less loops', () => { 40 | // const Div = styled.div` 41 | // .make-variants(@i:1) when (@i =< 3) { 42 | // .variant-@{i} { 43 | // width: @i * 40px; 44 | // height: @i * 20px; 45 | // background-color: orange; 46 | // margin-bottom: 10px; 47 | // } 48 | // .make-variants(@i + 1); // increment function 49 | // } 50 | // .make-variants(); 51 | // `; 52 | // expect(renderer.create(
).toJSON()).toMatchSnapshot(); 53 | // }); -------------------------------------------------------------------------------- /test/Component/NestedImports/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | "@babel/preset-react" 5 | ], 6 | "plugins": [ 7 | "../../../" 8 | ] 9 | } -------------------------------------------------------------------------------- /test/Component/NestedImports/NestedImports.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import renderer from 'react-test-renderer'; 3 | import styled from 'styled-components'; 4 | import 'jest-styled-components'; 5 | 6 | test('#29, nested imports', () => { 7 | const Div = styled.div` 8 | @import "tmp.less"; 9 | @import (reference) "reference.less"; 10 | &.light { 11 | color: @text-color; 12 | background: @layout-sider-background-light; 13 | } 14 | 15 | &.dark { 16 | background: @layout-header-background; 17 | color: @layout-trigger-color; 18 | } 19 | `; 20 | expect(renderer.create(
).toJSON()).toMatchSnapshot(); 21 | }); -------------------------------------------------------------------------------- /test/Component/NestedImports/__snapshots__/NestedImports.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`#29, nested imports 1`] = ` 4 | .c0 .test2 { 5 | color: test2; 6 | } 7 | 8 | .c0 .test { 9 | color: test; 10 | } 11 | 12 | .c0 .tmp { 13 | color: tmp; 14 | } 15 | 16 | .c0.light { 17 | color: pink; 18 | background: #222; 19 | } 20 | 21 | .c0.dark { 22 | background: #333; 23 | color: #444; 24 | } 25 | 26 |
29 | `; 30 | -------------------------------------------------------------------------------- /test/Component/NestedImports/reference.less: -------------------------------------------------------------------------------- 1 | .i-should-not-display { 2 | color: black; 3 | } 4 | 5 | @layout-sider-background-light: #222; 6 | @layout-header-background: #333; 7 | @layout-trigger-color: #444; -------------------------------------------------------------------------------- /test/Component/NestedImports/test.less: -------------------------------------------------------------------------------- 1 | @import "test2.less"; 2 | 3 | @testColor: test; 4 | .test { 5 | color: @testColor; 6 | } -------------------------------------------------------------------------------- /test/Component/NestedImports/test2.less: -------------------------------------------------------------------------------- 1 | @test2Color: test2; 2 | .test2 { 3 | color: @test2Color; 4 | } 5 | 6 | @text-color: pink; -------------------------------------------------------------------------------- /test/Component/NestedImports/tmp.less: -------------------------------------------------------------------------------- 1 | @import "./test.less"; 2 | 3 | @tmpColor: tmp; 4 | .tmp { 5 | color: @tmpColor; 6 | } 7 | -------------------------------------------------------------------------------- /test/Component/String.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import renderer from 'react-test-renderer'; 3 | import styled from 'styled-components'; 4 | import 'jest-styled-components'; 5 | 6 | test('String characters @url', () => { 7 | const Div = styled.div` 8 | background-image: url('images/lamp-post.png?v=1'); 9 | `; 10 | expect(renderer.create(
).toJSON()).toMatchSnapshot(); 11 | }); -------------------------------------------------------------------------------- /test/Component/StyledComponent.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import renderer from 'react-test-renderer'; 3 | import styled, {css} from "styled-components"; 4 | import 'jest-styled-components'; 5 | 6 | test('Support the styled-components syntax', () => { 7 | const Div = styled.div` 8 | width: ${400}px; 9 | width: 400px; 10 | border: 1px solid #000; 11 | border: ${"1px"} ${"solid"} ${"#000"}; 12 | background: ${props => props.primary ? 'palevioletred' : 'white'}; 13 | color: ${props => props.primary ? 'white' : 'palevioletred'}; 14 | color2: ${props => props.noChild && "#5cb85c"}; // @brand-success 15 | color3: @color; 16 | ${props => props.disabled ? "" : "box-shadow: inset 0 0 0 30px rgba(255, 255, 255, 0.3)"}; 17 | `; 18 | expect(renderer.create(
).toJSON()).toMatchSnapshot(); 19 | }); 20 | 21 | test('Attaching additional props', () => { 22 | const Input = styled.input.attrs(props => ({ 23 | // we can define static props 24 | type: 'password', 25 | 26 | // or we can define dynamic ones 27 | margin: props.size || '1em', 28 | padding: props.size || '1em' 29 | }))` 30 | color: palevioletred; 31 | font-size: 1em; 32 | border: 2px solid palevioletred; 33 | border-radius: 3px; 34 | 35 | /* here we use the dynamically computed props */ 36 | margin: ${props => props.margin}; 37 | padding: ${props => props.padding}; 38 | 39 | margin2: @margin; 40 | padding2: @padding; 41 | `; 42 | expect(renderer.create(
43 | 44 |
45 | 46 |
).toJSON()).toMatchSnapshot(); 47 | }); 48 | 49 | test('Support the styled-components css', () => { 50 | const Div = styled.div` 51 | padding: @padding; 52 | ${props => props.bordered && css` 53 | border: 1px solid @border; 54 | `} 55 | `; 56 | expect(renderer.create(
).toJSON()).toMatchSnapshot(); 57 | expect(renderer.create(
).toJSON()).toMatchSnapshot(); 58 | }); 59 | 60 | test('Support @media queries', () => { 61 | const Div = styled.div` 62 | @media screen and (min-width: 900px) { 63 | article { 64 | padding: @padding; 65 | } 66 | } 67 | `; 68 | expect(renderer.create(
).toJSON()).toMatchSnapshot(); 69 | }); 70 | 71 | test('Support &&&', () => { 72 | const Div = styled.div` 73 | &&& { 74 | color: palevioletred; 75 | font-weight: @bold; 76 | } 77 | &&&second { 78 | color: red; 79 | } 80 | `; 81 | expect(renderer.create(
).toJSON()).toMatchSnapshot(); 82 | expect(renderer.create(
).toJSON()).toMatchSnapshot(); 83 | }); 84 | 85 | test('Support Element', () => { 86 | const Div = styled.div` 87 | a { 88 | color: red; 89 | &.is-focused:not(.is-open) > .Select-control { 90 | cursor: text; 91 | } 92 | } 93 | `; 94 | expect(renderer.create(
).toJSON()).toMatchSnapshot(); 95 | }); 96 | 97 | test('Support placeholder on the root', () => { 98 | const Div = styled.div` 99 | &::placeholder { 100 | color: HSL(216, 15%, 65%); 101 | } 102 | `; 103 | expect(renderer.create(
).toJSON()).toMatchSnapshot(); 104 | }); 105 | 106 | test('Support styling other components', () => { 107 | const Child1 = styled.div` 108 | color: brown; 109 | `; 110 | const Child2 = styled.div` 111 | color: red; 112 | `; 113 | const Div = styled.div` 114 | ${Child1}, 115 | ${Child2}{ 116 | width: 400px; 117 | } 118 | `; 119 | expect(renderer.create(
).toJSON()).toMatchSnapshot(); 120 | }); 121 | 122 | test('Support SC mixins', () => { 123 | const hover = css` 124 | &:hover { 125 | text-decoration: underline; 126 | } 127 | `; 128 | const Icon = styled.div``; 129 | const Div = styled.div` 130 | ${hover}; 131 | ${props => hover}; 132 | border-bottom: @color; 133 | 134 | &:hover, &:hover ${Icon} { 135 | color: inherit; 136 | } 137 | `; 138 | expect(renderer.create(
).toJSON()).toMatchSnapshot(); 139 | }); 140 | 141 | test('#20, not', () => { 142 | const Div = styled.div` 143 | box-shadow: if(not (@disabled), inset 0 0 0 30px red); 144 | `; 145 | expect(renderer.create(
).toJSON()).toMatchSnapshot(); 146 | expect(renderer.create(
).toJSON()).toMatchSnapshot(); 147 | }); 148 | 149 | test('#20, box-shadow', () => { 150 | const Div = styled.div` 151 | box-shadow: if(not @disabled, inset 0 0 0 30px rgba(255, 255, 255, 0.5)); 152 | `; 153 | expect(renderer.create(
).toJSON()).toMatchSnapshot(); 154 | expect(renderer.create(
).toJSON()).toMatchSnapshot(); 155 | }); 156 | 157 | test('#39, box-shadow', () => { 158 | const Div = styled.div` 159 | .column { 160 | &.frozen&:empty::before { 161 | content: '\\200B'; 162 | } 163 | } 164 | `; 165 | expect(renderer.create(
).toJSON()).toMatchSnapshot(); 166 | }); 167 | 168 | test('Allow rules missing the last semi-colon', () => { 169 | const Div = styled.div` 170 | missing-semi-colon: @yes 171 | `; 172 | expect(renderer.create(
).toJSON()).toMatchSnapshot(); 173 | }); -------------------------------------------------------------------------------- /test/Component/Type.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import renderer from 'react-test-renderer'; 3 | import styled from 'styled-components'; 4 | import 'jest-styled-components'; 5 | 6 | test('Returns true if a value is a number, false otherwise.', () => { 7 | const Div = styled.div` 8 | number1: isnumber(#ff0); // false 9 | number2: isnumber(blue); // false 10 | number3: isnumber("string"); // false 11 | number4: isnumber(1234); // true 12 | number5: isnumber(56px); // true 13 | number6: isnumber(7.8%); // true 14 | number7: isnumber(keyword); // false 15 | number8: isnumber(url("http://yadi")); // false 16 | `; 17 | expect(renderer.create(
).toJSON()).toMatchSnapshot(); 18 | 19 | const Div2 = styled.div` 20 | number1: isnumber(@v1); // false 21 | number2: isnumber(@v2); // false 22 | number3: isnumber(@v3); // false 23 | number4: isnumber(@v4); // true 24 | number5: isnumber(@v5); // true 25 | number6: isnumber(@v6); // true 26 | number7: isnumber(@v7); // false 27 | number8: isnumber(@v8); // false 28 | `; 29 | expect(renderer.create().toJSON()).toMatchSnapshot(); 30 | }); 31 | 32 | test('Returns true if a value is a string, false otherwise.', () => { 33 | const Div = styled.div` 34 | string1: isstring(#ff0); // false 35 | string2: isstring(blue); // false 36 | string3: isstring("string"); // true 37 | string4: isstring(1234); // false 38 | string5: isstring(56px); // false 39 | string6: isstring(7.8%); // false 40 | string7: isstring(keyword); // false 41 | string8: isstring(url("http://yadi")); // false 42 | `; 43 | expect(renderer.create(
).toJSON()).toMatchSnapshot(); 44 | 45 | const Div2 = styled.div` 46 | string1: isstring(@v1); // false 47 | string2: isstring(@v2); // false 48 | string3: isstring(@v3); // true 49 | string4: isstring(@v4); // false 50 | string5: isstring(@v5); // false 51 | string6: isstring(@v6); // false 52 | string7: isstring(@v7); // false 53 | string1: isstring(@v8); // false 54 | `; 55 | expect(renderer.create().toJSON()).toMatchSnapshot(); 56 | }); 57 | 58 | test('Returns true if a value is a color, false otherwise.', () => { 59 | const Div = styled.div` 60 | v1: iscolor(#ff0); // true 61 | v2: iscolor(blue); // true 62 | v3: iscolor("string"); // false 63 | v4: iscolor(1234567); // false 64 | v5: iscolor(56px); // false 65 | v6: iscolor(7.8%); // false 66 | v7: iscolor(keyword); // false 67 | v8: iscolor(url("http://yadi")); // false 68 | `; 69 | expect(renderer.create(
).toJSON()).toMatchSnapshot(); 70 | 71 | const Div2 = styled.div` 72 | v1: iscolor(@v1); // true 73 | v2: iscolor(@v2); // true 74 | v3: iscolor(@v3); // false 75 | v4: iscolor(@v4); // false 76 | v5: iscolor(@v5); // false 77 | v6: iscolor(@v6); // false 78 | v7: iscolor(@v7); // false 79 | v8: iscolor(@v8); // false 80 | `; 81 | expect(renderer.create().toJSON()).toMatchSnapshot(); 82 | }); 83 | 84 | test('Returns true if a value is a number in pixels, false otherwise.', () => { 85 | const Div = styled.div` 86 | is: ispixel(56px); 87 | `; 88 | expect(renderer.create(
).toJSON()).toMatchSnapshot(); 89 | 90 | const Div2 = styled.div` 91 | is: ispixel(@v); 92 | `; 93 | expect(renderer.create().toJSON()).toMatchSnapshot(); 94 | }); 95 | 96 | test('Returns true if a value is an em value, false otherwise.', () => { 97 | const Div = styled.div` 98 | is: isem(56em); 99 | `; 100 | expect(renderer.create(
).toJSON()).toMatchSnapshot(); 101 | 102 | const Div2 = styled.div` 103 | is: isem(@v); 104 | `; 105 | expect(renderer.create().toJSON()).toMatchSnapshot(); 106 | }); 107 | 108 | test('Returns: true if value is a percentage value, false otherwise.', () => { 109 | const Div = styled.div` 110 | is: ispercentage(7.8%); 111 | `; 112 | expect(renderer.create(
).toJSON()).toMatchSnapshot(); 113 | 114 | const Div2 = styled.div` 115 | is: ispercentage(@v); 116 | `; 117 | expect(renderer.create().toJSON()).toMatchSnapshot(); 118 | }); 119 | 120 | test.skip('Returns: true if value is a number in specific units, false otherwise.', () => { 121 | const Div = styled.div` 122 | is: isunit(11px, px); 123 | `; 124 | expect(renderer.create(
).toJSON()).toMatchSnapshot(); 125 | 126 | const Div2 = styled.div` 127 | is: isunit(@v, %); 128 | width: @v; 129 | `; 130 | expect(renderer.create().toJSON()).toMatchSnapshot(); 131 | }); -------------------------------------------------------------------------------- /test/Component/Variable.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import renderer from 'react-test-renderer'; 3 | import styled from 'styled-components'; 4 | import 'jest-styled-components'; 5 | 6 | test('Takes the color from props', () => { 7 | const Div = styled.div` 8 | background-color: chocolate; 9 | color: @primary; 10 | `; 11 | const tree = renderer.create(
).toJSON(); 12 | expect(tree).toMatchSnapshot(); 13 | }); 14 | 15 | test('Replaces single variable in linear-gradient', () => { 16 | const Div = styled.div` 17 | background: linear-gradient(@start 0%, @end 100%); 18 | `; 19 | const tree = renderer.create(
).toJSON(); 20 | expect(tree).toMatchSnapshot(); 21 | }); 22 | 23 | test('Use the color defiened in the component', () => { 24 | const Div = styled.div` 25 | @main: palevioletred; 26 | color: @main; 27 | `; 28 | expect(renderer.create(
).toJSON()).toMatchSnapshot(); 29 | }); 30 | 31 | test('#32, Fail to escape variable with string and variable', () => { 32 | const Div = styled.div` 33 | @font-family-no-number : "Chinese Quote", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", Helvetica, Arial, sans-serif; 34 | @font-family : "Monospaced Number", @font-family-no-number; 35 | font-family: @font-family; 36 | `; 37 | expect(renderer.create(
).toJSON()).toMatchSnapshot(); 38 | }); 39 | 40 | test('#32, Fail to escape variable with string and variable from import', () => { 41 | const Div = styled.div` 42 | @import (reference) "import.less"; 43 | font-family: @font-family; 44 | `; 45 | expect(renderer.create(
).toJSON()).toMatchSnapshot(); 46 | expect(renderer.create(
).toJSON()).toMatchSnapshot(); 47 | }); 48 | 49 | test('Nested variables', () => { 50 | const Div = styled.div` 51 | @font-family3 : "Third"; 52 | @font-family2 : "Number", @font-family3; 53 | @font-family1 : "Monospaced", @font-family2; 54 | font-family: "root", @font-family1; 55 | `; 56 | expect(renderer.create(
).toJSON()).toMatchSnapshot(); 57 | expect(renderer.create(
).toJSON()).toMatchSnapshot(); 58 | }); 59 | 60 | test('#34', () => { 61 | const Link = styled.div``; 62 | const antBtnClassName = "ant-btn"; 63 | const size = {xlarge: "3.2rem"}; 64 | const AntButton = styled(Link)` 65 | fluid: @fluid; 66 | &.${antBtnClassName} { 67 | font-size: ${size.xlarge}; 68 | font-weight: 700; 69 | width: 40px; 70 | text-decoration: none; 71 | padding: @padding; 72 | 73 | &, 74 | &:hover, 75 | &:focus, 76 | &:active, 77 | &.active { 78 | background: none; 79 | border-color: transparent; 80 | } 81 | } 82 | `; 83 | const Div = styled(({fluid, ...rest}) => )` 84 | && { 85 | width: if(@fluid, 100%, auto); 86 | } 87 | `; 88 | expect(renderer.create(
).toJSON()).toMatchSnapshot(); 89 | expect(renderer.create(
).toJSON()).toMatchSnapshot(); 90 | }); -------------------------------------------------------------------------------- /test/Component/__snapshots__/Boolean.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Sets a complex variable in an if check 1`] = ` 4 |
7 | `; 8 | 9 | exports[`Sets a complex variable in an if check 2`] = ` 10 | .c0 { 11 | cursor: not-allowed; 12 | } 13 | 14 |
18 | `; 19 | 20 | exports[`Sets color to the inverse of the bg luma 1`] = ` 21 | .c0 { 22 | background: #001F3F; 23 | color: white; 24 | } 25 | 26 |
30 | `; 31 | 32 | exports[`Sets color to the inverse of the bg luma 2`] = ` 33 | .c0 { 34 | background: white; 35 | color: black; 36 | } 37 | 38 |
41 | `; 42 | 43 | exports[`Sets conditional margins 1`] = ` 44 |
47 | `; 48 | 49 | exports[`Sets conditional margins 2`] = ` 50 | .c0 { 51 | margin: 6px; 52 | } 53 | 54 |
57 | `; 58 | 59 | exports[`Sets conditional margins with variables 1`] = ` 60 | .c0 { 61 | margin: 0px; 62 | } 63 | 64 |
67 | `; 68 | 69 | exports[`Sets conditional margins with variables 2`] = ` 70 | .c0 { 71 | margin: 20px; 72 | } 73 | 74 |
77 | `; 78 | -------------------------------------------------------------------------------- /test/Component/__snapshots__/Color.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Calculates the luma (perceptual brightness) of a color object. 1`] = ` 4 | .c0 { 5 | luma: 44.111615679100964; 6 | luma: 44.111615679100964; 7 | } 8 | 9 |
13 | `; 14 | 15 | exports[`Calculates the value of the luma without gamma correction. 1`] = ` 16 | .c0 { 17 | luminance: 65.28078431372549; 18 | luminance: 65.28078431372549; 19 | luminance: 32.640392156862745; 20 | } 21 | 22 |
26 | `; 27 | 28 | exports[`Choose which of two colors provides the greatest contrast with another. 1`] = ` 29 | .c0 { 30 | a: #000; 31 | b: #FFF; 32 | c: #dddddd; 33 | d: #000000; 34 | e: #ffffff; 35 | } 36 | 37 |
40 | `; 41 | 42 | exports[`Creates a hex representation of a color in #AARRGGBB 1`] = ` 43 | .c0 { 44 | argb: #805a1794; 45 | argb: #805a1794; 46 | } 47 | 48 |
52 | `; 53 | 54 | exports[`Creates an opaque color object from decimal red, green and blue (RGB) values. 1`] = ` 55 | .c0 { 56 | rgb: #5a8120; 57 | rgba: rgba(90,129,32,0.5); 58 | rgb: #5a8120; 59 | rgba: rgba(90,129,32,0.5); 60 | rgb: rgb(90,129,32); 61 | rgba: rgba(90,129,32,0.5); 62 | rgb: rgb(90,129,32); 63 | rgba: rgba(90,129,32,0.5); 64 | rgb: rgb(255,255,255); 65 | } 66 | 67 |
71 | `; 72 | 73 | exports[`Creates an opaque color object from hue, saturation and lightness (HSL) values. 1`] = ` 74 | .c0 { 75 | hsl: #80ff00; 76 | hsl1: #80ff00; 77 | new: #e6f1da; 78 | hsla: rgba(128,255,0,0.5); 79 | hsla: rgba(128,255,0,0.5); 80 | } 81 | 82 |
85 | `; 86 | 87 | exports[`Creates an opaque color object from hue, saturation and value (HSV) values. 1`] = ` 88 | .c0 { 89 | hsv1: #408000; 90 | hsv2: #408000; 91 | hsva1: rgba(64,128,0,0.5); 92 | hsva2: rgba(64,128,0,0.5); 93 | } 94 | 95 |
98 | `; 99 | 100 | exports[`Decrease the lightness of a color in the HSL color space by an absolute amount. 1`] = ` 101 | .c0 { 102 | darken: rgb(77,138,15); 103 | darken: rgb(77,138,15); 104 | darken: rgb(77,138,15); 105 | darkena: rgba(77,138,15,0.5); 106 | darkena: rgba(77,138,15,0.5); 107 | darkena: rgba(77,138,15,0.5); 108 | } 109 | 110 |
114 | `; 115 | 116 | exports[`Decrease the saturation of a color in the HSL color space by an absolute amount. 1`] = ` 117 | .c0 { 118 | desaturate: rgb(128,204,51); 119 | desaturate: rgb(128,204,51); 120 | desaturate: rgb(128,204,51); 121 | desaturatea: rgba(128,204,51,0.5); 122 | desaturatea: rgba(128,204,51,0.5); 123 | desaturatea: rgba(128,204,51,0.5); 124 | } 125 | 126 |
130 | `; 131 | 132 | exports[`Decrease the transparency (or increase the opacity) of a color, making it more opaque. Has no effect on opaque colors. 1`] = ` 133 | .c0 { 134 | fadein: rgba(128,242,13,0.6); 135 | fadein: rgba(128,242,13,0.6); 136 | fadein: rgba(128,242,13,0.6); 137 | fadein: rgba(128,242,13,0.55); 138 | fadein: rgba(128,242,13,0.55); 139 | fadein: rgba(128,242,13,0.55); 140 | fadein: rgb(128,242,13); 141 | fadein: rgb(128,242,13); 142 | } 143 | 144 |
149 | `; 150 | 151 | exports[`Extract and reconstruct an hsl color. 1`] = ` 152 | .c0 { 153 | col-before: #80bf40; 154 | col-after: #80bf40; 155 | } 156 | 157 |
160 | `; 161 | 162 | exports[`Extract and reconstruct an hsl color. 2`] = ` 163 | .c0 { 164 | col-before: #80ff00; 165 | col-after: #80ff00; 166 | } 167 | 168 |
171 | `; 172 | 173 | exports[`Extract and reconstruct an hsv color. 1`] = ` 174 | .c0 { 175 | col-before: #608040; 176 | col-after: #608040; 177 | } 178 | 179 |
182 | `; 183 | 184 | exports[`Extract and reconstruct an hsv color. 2`] = ` 185 | .c0 { 186 | col-before: #80ff00; 187 | col-after: #80ff00; 188 | } 189 | 190 |
193 | `; 194 | 195 | exports[`Extracts the alpha channel of a color object. 1`] = ` 196 | .c0 { 197 | alpha: 0.5; 198 | alpha: 0.5; 199 | alpha: 0.5; 200 | } 201 | 202 |
207 | `; 208 | 209 | exports[`Extracts the blue channel of a color object. 1`] = ` 210 | .c0 { 211 | blue: 30; 212 | blue: 30; 213 | blue: 30; 214 | } 215 | 216 |
221 | `; 222 | 223 | exports[`Extracts the green channel of a color object. 1`] = ` 224 | .c0 { 225 | green: 20; 226 | green: 20; 227 | green: 20; 228 | } 229 | 230 |
235 | `; 236 | 237 | exports[`Extracts the hue channel of a color object in the HSL color space. 1`] = ` 238 | .c0 { 239 | hue: 90; 240 | hue: 90; 241 | hue: 90; 242 | hue: 90; 243 | } 244 | 245 |
249 | `; 250 | 251 | exports[`Extracts the hue channel of a color object in the HSV color space. 1`] = ` 252 | .c0 { 253 | hsvhue: 90; 254 | hsvhue: 90; 255 | hsvhue: 90; 256 | hsvhue: 90; 257 | } 258 | 259 |
263 | `; 264 | 265 | exports[`Extracts the lightness channel of a color object in the HSL color space. 1`] = ` 266 | .c0 { 267 | lightness: 50%; 268 | lightness: 50%; 269 | lightness: 50%; 270 | lightness: 50%; 271 | lightness: 50%; 272 | } 273 | 274 |
278 | `; 279 | 280 | exports[`Extracts the red channel of a color object. 1`] = ` 281 | .c0 { 282 | red: 10; 283 | red: 10; 284 | red: 10; 285 | } 286 | 287 |
292 | `; 293 | 294 | exports[`Extracts the saturation channel of a color object in the HSL color space. 1`] = ` 295 | .c0 { 296 | saturation: 100%; 297 | saturation: 100%; 298 | saturation: 100%; 299 | saturation: 100%; 300 | saturation: 50%; 301 | } 302 | 303 |
307 | `; 308 | 309 | exports[`Extracts the saturation channel of a color object in the HSV color space. 1`] = ` 310 | .c0 { 311 | hsvsaturation: 100%; 312 | hsvsaturation: 100%; 313 | hsvsaturation: 100%; 314 | hsvsaturation: 100%; 315 | hsvsaturation: 50%; 316 | } 317 | 318 |
322 | `; 323 | 324 | exports[`Extracts the value channel of a color object in the HSV color space. 1`] = ` 325 | .c0 { 326 | hsvvalue: 50%; 327 | hsvvalue: 50%; 328 | hsvvalue: 50%; 329 | hsvvalue: 50%; 330 | hsvvalue: 50%; 331 | } 332 | 333 |
337 | `; 338 | 339 | exports[`Increase the lightness of a color in the HSL color space by an absolute amount. 1`] = ` 340 | .c0 { 341 | lighten: rgb(179,240,117); 342 | lighten: rgb(179,240,117); 343 | lighten: rgb(179,240,117); 344 | lightena: rgba(179,240,117,0.5); 345 | lightena: rgba(179,240,117,0.5); 346 | lightena: rgba(179,240,117,0.5); 347 | } 348 | 349 |
353 | `; 354 | 355 | exports[`Increase the saturation of a color in the HSL color space by an absolute amount. 1`] = ` 356 | .c0 { 357 | saturate: rgb(128,255,0); 358 | saturate: rgb(128,255,0); 359 | saturate: rgb(128,255,0); 360 | saturatea: rgba(128,255,0,0.5); 361 | saturatea: rgba(128,255,0,0.5); 362 | saturatea: rgba(128,255,0,0.5); 363 | } 364 | 365 |
369 | `; 370 | 371 | exports[`Increase the transparency (or decrease the opacity) of a color, making it less opaque. 1`] = ` 372 | .c0 { 373 | fadeout: rgba(128,242,13,0.4); 374 | fadeout: rgba(128,242,13,0.4); 375 | fadeout: rgba(128,242,13,0.4); 376 | fadeout: rgba(128,242,13,0.45); 377 | fadeout: rgba(128,242,13,0.45); 378 | fadeout: rgba(128,242,13,0.45); 379 | fadeout: rgba(128,242,13,0); 380 | fadeout: rgba(128,242,13,0); 381 | } 382 | 383 |
388 | `; 389 | 390 | exports[`Mix color with black in variable proportion. 1`] = ` 391 | .c0 { 392 | no-alpha: rgb(0,64,128); 393 | no-alpha: rgb(0,64,128); 394 | } 395 | 396 |
400 | `; 401 | 402 | exports[`Mix color with white in variable proportion. 1`] = ` 403 | .c0 { 404 | no-alpha: rgb(128,191,255); 405 | no-alpha: rgb(128,191,255); 406 | } 407 | 408 |
412 | `; 413 | 414 | exports[`Mix two colors together in variable proportion. Opacity is included in the calculations. 1`] = ` 415 | .c0 { 416 | mix: rgb(128,0,128); 417 | mix: rgb(128,0,128); 418 | mix: rgb(50,50,0); 419 | mixa: rgba(128,0,128,0.5); 420 | mixa: rgba(128,0,128,0.5); 421 | mixa: rgba(50,50,0,0.5); 422 | } 423 | 424 |
427 | `; 428 | 429 | exports[`Remove all saturation from a color in the HSL color space. 1`] = ` 430 | .c0 { 431 | greyscale1: rgb(128,128,128); 432 | greyscale2: rgb(128,128,128); 433 | } 434 | 435 |
439 | `; 440 | 441 | exports[`Rotate the hue angle of a color in either direction. 1`] = ` 442 | .c0 { 443 | spin: rgb(242,165,13); 444 | spin: rgb(242,13,90); 445 | spin: rgb(242,165,13); 446 | spin: rgb(242,13,90); 447 | spin: rgb(242,165,13); 448 | spin: rgb(242,13,90); 449 | spin: rgb(242,166,13); 450 | spin: rgb(242,51,13); 451 | spina: rgba(242,165,13,0.5); 452 | spina: rgba(242,13,90,0.5); 453 | spina: rgba(242,166,13,0.5); 454 | } 455 | 456 |
460 | `; 461 | 462 | exports[`Set the absolute opacity of a color. Can be applied to colors whether they already have an opacity value or not. 1`] = ` 463 | .c0 { 464 | fade: rgba(128,242,13,0.1); 465 | fade: rgba(128,242,13,0.1); 466 | fade: rgba(128,242,13,0); 467 | fade: rgb(128,242,13); 468 | } 469 | 470 |
474 | `; 475 | -------------------------------------------------------------------------------- /test/Component/__snapshots__/Condition.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`If with function 1`] = ` 4 | .c0 { 5 | cursor: pointer; 6 | } 7 | 8 |
12 | `; 13 | 14 | exports[`Test = 1`] = ` 15 | .c0 { 16 | opacity1: 1; 17 | } 18 | 19 |
22 | `; 23 | 24 | exports[`Test = 2`] = ` 25 | .c0 { 26 | opacity1: 0; 27 | } 28 | 29 |
32 | `; 33 | 34 | exports[`Test = 3`] = ` 35 | .c0 { 36 | opacity1: 1; 37 | } 38 | 39 |
42 | `; 43 | 44 | exports[`Test = 4`] = ` 45 | .c0 { 46 | opacity1: 0; 47 | } 48 | 49 |
52 | `; 53 | 54 | exports[`Test and 1`] = ` 55 | .c0 { 56 | opacity1: 0; 57 | opacity2: 0; 58 | } 59 | 60 |
63 | `; 64 | 65 | exports[`Test and 2`] = ` 66 | .c0 { 67 | opacity1: 0; 68 | opacity2: 0; 69 | } 70 | 71 |
74 | `; 75 | 76 | exports[`Test and 3`] = ` 77 | .c0 { 78 | opacity1: 0; 79 | opacity2: 0; 80 | } 81 | 82 |
85 | `; 86 | 87 | exports[`Test and 4`] = ` 88 | .c0 { 89 | opacity1: 1; 90 | opacity2: 1; 91 | } 92 | 93 |
96 | `; 97 | 98 | exports[`Test greater than 1`] = ` 99 | .c0 { 100 | opacity: 0; 101 | opacity: 0; 102 | opacity: 1; 103 | opacity: 1; 104 | } 105 | 106 |
109 | `; 110 | 111 | exports[`Test greater than 2`] = ` 112 | .c0 { 113 | opacity: 1; 114 | opacity: 1; 115 | opacity: 0; 116 | opacity: 0; 117 | } 118 | 119 |
122 | `; 123 | 124 | exports[`Test or 1`] = ` 125 | .c0 { 126 | opacity1: 0; 127 | opacity2: 0; 128 | } 129 | 130 |
133 | `; 134 | 135 | exports[`Test or 2`] = ` 136 | .c0 { 137 | opacity1: 1; 138 | opacity2: 1; 139 | } 140 | 141 |
144 | `; 145 | 146 | exports[`Test or 3`] = ` 147 | .c0 { 148 | opacity1: 1; 149 | opacity2: 1; 150 | } 151 | 152 |
155 | `; 156 | 157 | exports[`Test or 4`] = ` 158 | .c0 { 159 | opacity1: 1; 160 | opacity2: 1; 161 | } 162 | 163 |
166 | `; 167 | -------------------------------------------------------------------------------- /test/Component/__snapshots__/Extend.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Extend a styled div 1`] = ` 4 | .c0 { 5 | color: red; 6 | } 7 | 8 |
11 | `; 12 | 13 | exports[`Extend a styled div 2`] = ` 14 | .c0 { 15 | color: red; 16 | color: red; 17 | } 18 | 19 |
22 | `; 23 | 24 | exports[`Extend styled-components example 1`] = ` 25 | .c0 { 26 | color: palevioletred; 27 | font-size: 1em; 28 | margin: 1em; 29 | padding: 0.25em 1em; 30 | border: 2px solid palevioletred; 31 | border-radius: 3px; 32 | } 33 | 34 | .c1 { 35 | color: palevioletred; 36 | font-size: 1em; 37 | margin: 1em; 38 | padding: 0.25em 1em; 39 | border: 2px solid palevioletred; 40 | border-radius: 3px; 41 | color: tomato; 42 | border-color: tomato; 43 | } 44 | 45 |
46 | 51 | 57 |
58 | `; 59 | -------------------------------------------------------------------------------- /test/Component/__snapshots__/Functional.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Darkens the colors in linear-gradient 1`] = ` 4 | .c0 { 5 | background: linear-gradient(rgb(255,102,102) 0%,rgb(0,0,153) 100%); 6 | } 7 | 8 |
13 | `; 14 | 15 | exports[`Darkens the primary color 20% 1`] = ` 16 | .c0 { 17 | color: rgb(153,0,0); 18 | } 19 | 20 |
23 | `; 24 | -------------------------------------------------------------------------------- /test/Component/__snapshots__/ImportFromScopedNPM.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Support import from scoped npm 1`] = ` 4 | .c0 { 5 | color: yellow; 6 | } 7 | 8 |
11 | `; 12 | -------------------------------------------------------------------------------- /test/Component/__snapshots__/Imports.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Support import 1`] = ` 4 | .c0 { 5 | color: green; 6 | background: #900; 7 | border: 1px solid green; 8 | } 9 | 10 | .c0 .foo { 11 | background: #900; 12 | border: 1px solid green; 13 | } 14 | 15 |
18 | `; 19 | 20 | exports[`Support import 2`] = ` 21 | .c0 { 22 | color: red; 23 | background: #900; 24 | border: 1px solid red; 25 | } 26 | 27 | .c0 .foo { 28 | background: #900; 29 | border: 1px solid red; 30 | } 31 | 32 |
36 | `; 37 | 38 | exports[`Support import reference 1`] = ` 39 | .c0 { 40 | color: green; 41 | background: #900; 42 | border: 1px solid green; 43 | } 44 | 45 |
48 | `; 49 | 50 | exports[`Support import reference 2`] = ` 51 | .c0 { 52 | color: red; 53 | background: #900; 54 | border: 1px solid red; 55 | } 56 | 57 |
61 | `; 62 | -------------------------------------------------------------------------------- /test/Component/__snapshots__/Javascript.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Support javascript 1`] = ` 4 | .c0 { 5 | color: green; 6 | background: #403d3c; 7 | border: 1px solid #403d3c; 8 | } 9 | 10 |
13 | `; 14 | 15 | exports[`Support javascript 2`] = ` 16 | .c0 { 17 | color: red; 18 | background: #403d3c; 19 | border: 1px solid #403d3c; 20 | } 21 | 22 |
26 | `; 27 | -------------------------------------------------------------------------------- /test/Component/__snapshots__/Less.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Less should still compile normally 1`] = ` 4 | "div { 5 | color: red; 6 | darken: #990000; 7 | } 8 | " 9 | `; 10 | -------------------------------------------------------------------------------- /test/Component/__snapshots__/List.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Returns the number of elements in a value list 1`] = ` 4 | .c0 { 5 | n: 4; 6 | n2: 3; 7 | } 8 | 9 |
12 | `; 13 | 14 | exports[`Returns the value at a specified position in a list. 1`] = ` 15 | .c0 { 16 | value: coconut; 17 | } 18 | 19 |
22 | `; 23 | -------------------------------------------------------------------------------- /test/Component/__snapshots__/Math.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Applies rounding. 1`] = ` 4 | .c0 { 5 | round: 2; 6 | round: 2; 7 | round: 2px; 8 | round: 2px; 9 | } 10 | 11 |
15 | `; 16 | 17 | exports[`Calculates absolute value of a number. Keeps units as they are. 1`] = ` 18 | .c0 { 19 | abs: 25cm; 20 | abs: 18.6%; 21 | abs: 25cm; 22 | abs: 18.6%; 23 | } 24 | 25 |
29 | `; 30 | 31 | exports[`Calculates arccosine (inverse of cosine) function. 1`] = ` 32 | .c0 { 33 | acos: 1.0471975511965979rad; 34 | acos: 0rad; 35 | acos: NaNrad; 36 | } 37 | 38 |
41 | `; 42 | 43 | exports[`Calculates arcsine (inverse of sine) function. 1`] = ` 44 | .c0 { 45 | asin: 0.5235987755982989rad; 46 | asin: 0rad; 47 | asin: NaNrad; 48 | } 49 | 50 |
53 | `; 54 | 55 | exports[`Calculates arctangent (inverse of tangent) function. 1`] = ` 56 | .c0 { 57 | atan: -1rad; 58 | atan: 0rad; 59 | atan: 1.525373rad; 60 | } 61 | 62 |
65 | `; 66 | 67 | exports[`Calculates cosine function. 1`] = ` 68 | .c0 { 69 | sin: 0.5403023058681398; 70 | deg: 0.9998476951563913; 71 | grad: 0.9998766324816606; 72 | turn: 0.30901699437494745; 73 | } 74 | 75 |
79 | `; 80 | 81 | exports[`Calculates sine function. 1`] = ` 82 | .c0 { 83 | sin: 0.8414709848078965; 84 | deg: 0.01745240643728351; 85 | grad: 0.015707317311820675; 86 | turn: 0.9510565162951535; 87 | } 88 | 89 |
93 | `; 94 | 95 | exports[`Calculates square root of a number. Keeps units as they are. 1`] = ` 96 | .c0 { 97 | sqrt: 5cm; 98 | sqrt: 5cm; 99 | } 100 | 101 |
105 | `; 106 | 107 | exports[`Calculates tangent function. 1`] = ` 108 | .c0 { 109 | tan: 1.5574077246549023; 110 | tan: 0.017455064928217585; 111 | tan: 0.015709255323664916; 112 | } 113 | 114 |
118 | `; 119 | 120 | exports[`Converts a floating point number into a percentage string. 1`] = ` 121 | .c0 { 122 | p: 50%; 123 | p: 50%; 124 | p: 50%; 125 | } 126 | 127 |
131 | `; 132 | 133 | exports[`Does some conditional math. 1`] = ` 134 | .c0 { 135 | a: 8; 136 | b: 2; 137 | } 138 | 139 |
142 | `; 143 | 144 | exports[`Does some math. 1`] = ` 145 | .c0 { 146 | width: 200px; 147 | width1: 150px; 148 | width1: 50px; 149 | width1: 5000px; 150 | width1: 2px; 151 | width2: 150px; 152 | width2: -50px; 153 | width2: 5000px; 154 | width2: 0.5px; 155 | width3: 300px; 156 | width3: 100px; 157 | } 158 | 159 |
162 | `; 163 | 164 | exports[`PI 1`] = ` 165 | .c0 { 166 | pi: 3.14159265; 167 | } 168 | 169 |
172 | `; 173 | 174 | exports[`Returns the highest of one or more values. 1`] = ` 175 | .c0 { 176 | max: 10; 177 | max: 42%; 178 | max: 72cm; 179 | } 180 | 181 |
184 | `; 185 | 186 | exports[`Returns the lowest of one or more values. 1`] = ` 187 | .c0 { 188 | min: 5; 189 | min: 2px; 190 | min: 3px; 191 | } 192 | 193 |
196 | `; 197 | 198 | exports[`Returns the value of the first argument modulus second argument. 1`] = ` 199 | .c0 { 200 | mod: NaNcm; 201 | mod: 5cm; 202 | mod: -1%; 203 | mod: 2; 204 | } 205 | 206 |
211 | `; 212 | 213 | exports[`Returns the value of the first argument raised to the power of the second argument. 1`] = ` 214 | .c0 { 215 | pow: 1cm; 216 | pow: 0.04; 217 | pow: 5; 218 | pow: NaN; 219 | pow: NaN%; 220 | pow: 625; 221 | } 222 | 223 |
228 | `; 229 | 230 | exports[`Rounds down to the next lowest integer. 1`] = ` 231 | .c0 { 232 | floor: 2; 233 | floor: 2; 234 | floor: 2em; 235 | floor: 2em; 236 | } 237 | 238 |
242 | `; 243 | 244 | exports[`Rounds up to the next highest integer. 1`] = ` 245 | .c0 { 246 | ceil: 3; 247 | ceil: 3; 248 | ceil: 3px; 249 | ceil: 3px; 250 | } 251 | 252 |
256 | `; 257 | -------------------------------------------------------------------------------- /test/Component/__snapshots__/Mixins.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Support inline mixins 1`] = ` 4 | .c0 .my-mixin { 5 | color: red; 6 | } 7 | 8 | .c0 .class { 9 | color: red; 10 | background: white; 11 | } 12 | 13 |
17 | `; 18 | -------------------------------------------------------------------------------- /test/Component/__snapshots__/String.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`String characters @url 1`] = ` 4 | .c0 { 5 | background-image: url('images/lamp-post.png?v=1'); 6 | } 7 | 8 |
11 | `; 12 | -------------------------------------------------------------------------------- /test/Component/__snapshots__/StyledComponent.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`#20, box-shadow 1`] = ` 4 |
7 | `; 8 | 9 | exports[`#20, box-shadow 2`] = ` 10 | .c0 { 11 | box-shadow: inset 0 0 0 30px rgba(255,255,255,0.5); 12 | } 13 | 14 |
18 | `; 19 | 20 | exports[`#20, not 1`] = ` 21 | .c0 { 22 | box-shadow: inset 0 0 0 30px red; 23 | } 24 | 25 |
28 | `; 29 | 30 | exports[`#20, not 2`] = ` 31 |
35 | `; 36 | 37 | exports[`#39, box-shadow 1`] = ` 38 | .c0 .column.frozen.column:empty::before { 39 | content: '\\200B'; 40 | } 41 | 42 |
45 | `; 46 | 47 | exports[`Allow rules missing the last semi-colon 1`] = ` 48 | .c0 { 49 | missing-semi-colon: yes; 50 | } 51 | 52 |
55 | `; 56 | 57 | exports[`Attaching additional props 1`] = ` 58 | .c0 { 59 | color: palevioletred; 60 | font-size: 1em; 61 | border: 2px solid palevioletred; 62 | border-radius: 3px; 63 | margin: 1em; 64 | padding: 1em; 65 | margin2: 1em; 66 | padding2: 1em; 67 | } 68 | 69 | .c1 { 70 | color: palevioletred; 71 | font-size: 1em; 72 | border: 2px solid palevioletred; 73 | border-radius: 3px; 74 | margin: 2em; 75 | padding: 2em; 76 | margin2: 2em; 77 | padding2: 2em; 78 | } 79 | 80 |
81 | 87 |
88 | 94 |
95 | `; 96 | 97 | exports[`Support &&& 1`] = ` 98 | .c0.c0.c0 { 99 | color: palevioletred; 100 | } 101 | 102 | .c0.c0.c0second { 103 | color: red; 104 | } 105 | 106 |
109 | `; 110 | 111 | exports[`Support &&& 2`] = ` 112 | .c0.c0.c0 { 113 | color: palevioletred; 114 | font-weight: bold; 115 | } 116 | 117 | .c0.c0.c0second { 118 | color: red; 119 | } 120 | 121 |
124 | `; 125 | 126 | exports[`Support @media queries 1`] = ` 127 | @media screen and (min-width:900px) { 128 | .c0 article { 129 | padding: 1rem; 130 | } 131 | } 132 | 133 |
137 | `; 138 | 139 | exports[`Support Element 1`] = ` 140 | .c0 a { 141 | color: red; 142 | } 143 | 144 | .c0 a.is-focused:not(.is-open) > .Select-control { 145 | cursor: text; 146 | } 147 | 148 |
151 | `; 152 | 153 | exports[`Support SC mixins 1`] = ` 154 | .c1 { 155 | border-bottom: red; 156 | } 157 | 158 | .c1:hover { 159 | -webkit-text-decoration: underline; 160 | text-decoration: underline; 161 | } 162 | 163 | .c1:hover { 164 | -webkit-text-decoration: underline; 165 | text-decoration: underline; 166 | } 167 | 168 | .c1:hover, 169 | .c1:hover.c0 { 170 | color: inherit; 171 | } 172 | 173 | 174 |
177 |
181 | 182 | `; 183 | 184 | exports[`Support placeholder on the root 1`] = ` 185 | .c0::-webkit-input-placeholder { 186 | color: #98a3b3; 187 | } 188 | 189 | .c0::-moz-placeholder { 190 | color: #98a3b3; 191 | } 192 | 193 | .c0:-ms-input-placeholder { 194 | color: #98a3b3; 195 | } 196 | 197 | .c0::placeholder { 198 | color: #98a3b3; 199 | } 200 | 201 |
204 | `; 205 | 206 | exports[`Support styling other components 1`] = ` 207 | .c2 { 208 | color: brown; 209 | } 210 | 211 | .c4 { 212 | color: red; 213 | } 214 | 215 | .c0 .c1, 216 | .c0 .c3 { 217 | width: 400px; 218 | } 219 | 220 |
223 |
226 |
229 |
230 | `; 231 | 232 | exports[`Support the styled-components css 1`] = ` 233 | .c0 { 234 | padding: 1rem; 235 | } 236 | 237 |
240 | `; 241 | 242 | exports[`Support the styled-components css 2`] = ` 243 | .c0 { 244 | padding: 1rem; 245 | border: 1px solid red; 246 | } 247 | 248 |
251 | `; 252 | 253 | exports[`Support the styled-components syntax 1`] = ` 254 | .c0 { 255 | width: 400px; 256 | width: 400px; 257 | border: 1px solid #000; 258 | border: 1px solid #000; 259 | background: palevioletred; 260 | color: white; 261 | color2: #5cb85c; 262 | color3: red; 263 | box-shadow: inset 0 0 0 30px rgba(255,255,255,0.3); 264 | } 265 | 266 |
270 | `; 271 | -------------------------------------------------------------------------------- /test/Component/__snapshots__/Type.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Returns true if a value is a color, false otherwise. 1`] = ` 4 | .c0 { 5 | v1: true; 6 | v2: true; 7 | v3: false; 8 | v4: false; 9 | v5: false; 10 | v6: false; 11 | v7: false; 12 | v8: false; 13 | } 14 | 15 |
18 | `; 19 | 20 | exports[`Returns true if a value is a color, false otherwise. 2`] = ` 21 | .c0 { 22 | v1: true; 23 | v2: true; 24 | v3: false; 25 | v4: false; 26 | v5: false; 27 | v6: false; 28 | v7: false; 29 | v8: false; 30 | } 31 | 32 |
35 | `; 36 | 37 | exports[`Returns true if a value is a number in pixels, false otherwise. 1`] = ` 38 | .c0 { 39 | is: true; 40 | } 41 | 42 |
45 | `; 46 | 47 | exports[`Returns true if a value is a number in pixels, false otherwise. 2`] = ` 48 | .c0 { 49 | is: true; 50 | } 51 | 52 |
55 | `; 56 | 57 | exports[`Returns true if a value is a number, false otherwise. 1`] = ` 58 | .c0 { 59 | number1: false; 60 | number2: false; 61 | number3: false; 62 | number4: true; 63 | number5: true; 64 | number6: true; 65 | number7: false; 66 | number8: false; 67 | } 68 | 69 |
72 | `; 73 | 74 | exports[`Returns true if a value is a number, false otherwise. 2`] = ` 75 | .c0 { 76 | number1: false; 77 | number2: false; 78 | number3: false; 79 | number4: true; 80 | number5: true; 81 | number6: true; 82 | number7: false; 83 | number8: false; 84 | } 85 | 86 |
89 | `; 90 | 91 | exports[`Returns true if a value is a string, false otherwise. 1`] = ` 92 | .c0 { 93 | string1: false; 94 | string2: false; 95 | string3: true; 96 | string4: false; 97 | string5: false; 98 | string6: false; 99 | string7: false; 100 | string8: false; 101 | } 102 | 103 |
106 | `; 107 | 108 | exports[`Returns true if a value is a string, false otherwise. 2`] = ` 109 | .c0 { 110 | string1: false; 111 | string2: false; 112 | string3: true; 113 | string4: false; 114 | string5: false; 115 | string6: false; 116 | string7: false; 117 | string1: false; 118 | } 119 | 120 |
123 | `; 124 | 125 | exports[`Returns true if a value is an em value, false otherwise. 1`] = ` 126 | .c0 { 127 | is: true; 128 | } 129 | 130 |
133 | `; 134 | 135 | exports[`Returns true if a value is an em value, false otherwise. 2`] = ` 136 | .c0 { 137 | is: true; 138 | } 139 | 140 |
143 | `; 144 | 145 | exports[`Returns: true if value is a number in specific units, false otherwise. 1`] = ` 146 | .c0 { 147 | is: true; 148 | } 149 | 150 |
153 | `; 154 | 155 | exports[`Returns: true if value is a number in specific units, false otherwise. 2`] = ` 156 | .c0 { 157 | is: false; 158 | width: 7.8%; 159 | } 160 | 161 |
164 | `; 165 | 166 | exports[`Returns: true if value is a percentage value, false otherwise. 1`] = ` 167 | .c0 { 168 | is: true; 169 | } 170 | 171 |
174 | `; 175 | 176 | exports[`Returns: true if value is a percentage value, false otherwise. 2`] = ` 177 | .c0 { 178 | is: true; 179 | } 180 | 181 |
184 | `; 185 | -------------------------------------------------------------------------------- /test/Component/__snapshots__/Variable.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`#32, Fail to escape variable with string and variable 1`] = ` 4 | .c0 { 5 | font-family: "Monospaced Number","Chinese Quote",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Helvetica Neue",Helvetica,Arial,sans-serif; 6 | } 7 | 8 |
11 | `; 12 | 13 | exports[`#32, Fail to escape variable with string and variable from import 1`] = ` 14 | .c0 { 15 | font-family: "Monospaced Number less","Chinese Quote",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Helvetica Neue",Helvetica,Arial,sans-serif; 16 | } 17 | 18 |
21 | `; 22 | 23 | exports[`#32, Fail to escape variable with string and variable from import 2`] = ` 24 | .c0 { 25 | font-family: "Monospaced Number less",font-family-no-number; 26 | } 27 | 28 |
31 | `; 32 | 33 | exports[`#34 1`] = ` 34 | .c0.ant-btn { 35 | font-size: 3.2rem; 36 | font-weight: 700; 37 | width: 40px; 38 | -webkit-text-decoration: none; 39 | text-decoration: none; 40 | } 41 | 42 | .c0.ant-btn, 43 | .c0.ant-btn:hover, 44 | .c0.ant-btn:focus, 45 | .c0.ant-btn:active, 46 | .c0.ant-btn.active { 47 | background: none; 48 | border-color: transparent; 49 | } 50 | 51 | .c1.c1 { 52 | width: auto; 53 | } 54 | 55 |
58 | `; 59 | 60 | exports[`#34 2`] = ` 61 | .c0.ant-btn { 62 | font-size: 3.2rem; 63 | font-weight: 700; 64 | width: 40px; 65 | -webkit-text-decoration: none; 66 | text-decoration: none; 67 | padding: 1rem; 68 | } 69 | 70 | .c0.ant-btn, 71 | .c0.ant-btn:hover, 72 | .c0.ant-btn:focus, 73 | .c0.ant-btn:active, 74 | .c0.ant-btn.active { 75 | background: none; 76 | border-color: transparent; 77 | } 78 | 79 | .c1.c1 { 80 | width: 100%; 81 | } 82 | 83 |
86 | `; 87 | 88 | exports[`Nested variables 1`] = ` 89 | .c0 { 90 | font-family: "root","Monospaced","Number","Third"; 91 | } 92 | 93 |
96 | `; 97 | 98 | exports[`Nested variables 2`] = ` 99 | .c0 { 100 | font-family: "root","Monospaced","Number",Arial; 101 | } 102 | 103 |
106 | `; 107 | 108 | exports[`Replaces single variable in linear-gradient 1`] = ` 109 | .c0 { 110 | background: linear-gradient(red 0%,blue 100%); 111 | } 112 | 113 |
118 | `; 119 | 120 | exports[`Takes the color from props 1`] = ` 121 | .c0 { 122 | background-color: chocolate; 123 | color: red; 124 | } 125 | 126 |
129 | `; 130 | 131 | exports[`Use the color defiened in the component 1`] = ` 132 | .c0 { 133 | color: palevioletred; 134 | } 135 | 136 |
139 | `; 140 | -------------------------------------------------------------------------------- /test/Component/__snapshots__/createGlobalStyle.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Should theme the global style 1`] = ` 4 | " 5 | /* sc-component-id: sc-global-2534925725 */ 6 | html{color:color;}" 7 | `; 8 | -------------------------------------------------------------------------------- /test/Component/base.less: -------------------------------------------------------------------------------- 1 | @base-color: #b5483d; 2 | .base-mixin { 3 | background: #39688f; 4 | } -------------------------------------------------------------------------------- /test/Component/color/bezierEasing.less: -------------------------------------------------------------------------------- 1 | /* stylelint-disable declaration-bang-space-before,no-duplicate-selectors */ 2 | .bezierEasingMixin() { 3 | @functions: ~`(function() { 4 | var NEWTON_ITERATIONS = 4; 5 | var NEWTON_MIN_SLOPE = 0.001; 6 | var SUBDIVISION_PRECISION = 0.0000001; 7 | var SUBDIVISION_MAX_ITERATIONS = 10; 8 | 9 | var kSplineTableSize = 11; 10 | var kSampleStepSize = 1.0 / (kSplineTableSize - 1.0); 11 | 12 | var float32ArraySupported = typeof Float32Array === 'function'; 13 | 14 | function A (aA1, aA2) { return 1.0 - 3.0 * aA2 + 3.0 * aA1; } 15 | function B (aA1, aA2) { return 3.0 * aA2 - 6.0 * aA1; } 16 | function C (aA1) { return 3.0 * aA1; } 17 | 18 | // Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2. 19 | function calcBezier (aT, aA1, aA2) { return ((A(aA1, aA2) * aT + B(aA1, aA2)) * aT + C(aA1)) * aT; } 20 | 21 | // Returns dx/dt given t, x1, and x2, or dy/dt given t, y1, and y2. 22 | function getSlope (aT, aA1, aA2) { return 3.0 * A(aA1, aA2) * aT * aT + 2.0 * B(aA1, aA2) * aT + C(aA1); } 23 | 24 | function binarySubdivide (aX, aA, aB, mX1, mX2) { 25 | var currentX, currentT, i = 0; 26 | do { 27 | currentT = aA + (aB - aA) / 2.0; 28 | currentX = calcBezier(currentT, mX1, mX2) - aX; 29 | if (currentX > 0.0) { 30 | aB = currentT; 31 | } else { 32 | aA = currentT; 33 | } 34 | } while (Math.abs(currentX) > SUBDIVISION_PRECISION && ++i < SUBDIVISION_MAX_ITERATIONS); 35 | return currentT; 36 | } 37 | 38 | function newtonRaphsonIterate (aX, aGuessT, mX1, mX2) { 39 | for (var i = 0; i < NEWTON_ITERATIONS; ++i) { 40 | var currentSlope = getSlope(aGuessT, mX1, mX2); 41 | if (currentSlope === 0.0) { 42 | return aGuessT; 43 | } 44 | var currentX = calcBezier(aGuessT, mX1, mX2) - aX; 45 | aGuessT -= currentX / currentSlope; 46 | } 47 | return aGuessT; 48 | } 49 | 50 | var BezierEasing = function (mX1, mY1, mX2, mY2) { 51 | if (!(0 <= mX1 && mX1 <= 1 && 0 <= mX2 && mX2 <= 1)) { 52 | throw new Error('bezier x values must be in [0, 1] range'); 53 | } 54 | 55 | // Precompute samples table 56 | var sampleValues = float32ArraySupported ? new Float32Array(kSplineTableSize) : new Array(kSplineTableSize); 57 | if (mX1 !== mY1 || mX2 !== mY2) { 58 | for (var i = 0; i < kSplineTableSize; ++i) { 59 | sampleValues[i] = calcBezier(i * kSampleStepSize, mX1, mX2); 60 | } 61 | } 62 | 63 | function getTForX (aX) { 64 | var intervalStart = 0.0; 65 | var currentSample = 1; 66 | var lastSample = kSplineTableSize - 1; 67 | 68 | for (; currentSample !== lastSample && sampleValues[currentSample] <= aX; ++currentSample) { 69 | intervalStart += kSampleStepSize; 70 | } 71 | --currentSample; 72 | 73 | // Interpolate to provide an initial guess for t 74 | var dist = (aX - sampleValues[currentSample]) / (sampleValues[currentSample + 1] - sampleValues[currentSample]); 75 | var guessForT = intervalStart + dist * kSampleStepSize; 76 | 77 | var initialSlope = getSlope(guessForT, mX1, mX2); 78 | if (initialSlope >= NEWTON_MIN_SLOPE) { 79 | return newtonRaphsonIterate(aX, guessForT, mX1, mX2); 80 | } else if (initialSlope === 0.0) { 81 | return guessForT; 82 | } else { 83 | return binarySubdivide(aX, intervalStart, intervalStart + kSampleStepSize, mX1, mX2); 84 | } 85 | } 86 | 87 | return function BezierEasing (x) { 88 | if (mX1 === mY1 && mX2 === mY2) { 89 | return x; // linear 90 | } 91 | // Because JavaScript number are imprecise, we should guarantee the extremes are right. 92 | if (x === 0) { 93 | return 0; 94 | } 95 | if (x === 1) { 96 | return 1; 97 | } 98 | return calcBezier(getTForX(x), mY1, mY2); 99 | }; 100 | }; 101 | 102 | this.colorEasing = BezierEasing(0.26, 0.09, 0.37, 0.18); 103 | // less 3 requires a return 104 | return ''; 105 | })()`; 106 | } 107 | // It is hacky way to make this function will be compiled preferentially by less 108 | // resolve error: `ReferenceError: colorPalette is not defined` 109 | // https://github.com/ant-design/ant-motion/issues/44 110 | .bezierEasingMixin(); 111 | -------------------------------------------------------------------------------- /test/Component/color/colorPalette.less: -------------------------------------------------------------------------------- 1 | /* stylelint-disable no-duplicate-selectors */ 2 | @import "bezierEasing"; 3 | @import "tinyColor"; 4 | 5 | // We create a very complex algorithm which take the place of original tint/shade color system 6 | // to make sure no one can understand it 👻 7 | // and create an entire color palette magicly by inputing just a single primary color. 8 | // We are using bezier-curve easing function and some color manipulations like tint/shade/darken/spin 9 | .colorPaletteMixin() { 10 | @functions: ~`(function() { 11 | var hueStep = 2; 12 | var saturationStep = 16; 13 | var saturationStep2 = 5; 14 | var brightnessStep1 = 5; 15 | var brightnessStep2 = 15; 16 | var lightColorCount = 5; 17 | var darkColorCount = 4; 18 | 19 | var getHue = function(hsv, i, isLight) { 20 | var hue; 21 | if (hsv.h >= 60 && hsv.h <= 240) { 22 | hue = isLight ? hsv.h - hueStep * i : hsv.h + hueStep * i; 23 | } else { 24 | hue = isLight ? hsv.h + hueStep * i : hsv.h - hueStep * i; 25 | } 26 | if (hue < 0) { 27 | hue += 360; 28 | } else if (hue >= 360) { 29 | hue -= 360; 30 | } 31 | return Math.round(hue); 32 | }; 33 | var getSaturation = function(hsv, i, isLight) { 34 | var saturation; 35 | if (isLight) { 36 | saturation = Math.round(hsv.s * 100) - saturationStep * i; 37 | } else if (i == darkColorCount) { 38 | saturation = Math.round(hsv.s * 100) + saturationStep; 39 | } else { 40 | saturation = Math.round(hsv.s * 100) + saturationStep2 * i; 41 | } 42 | if (saturation > 100) { 43 | saturation = 100; 44 | } 45 | if (isLight && i === lightColorCount && saturation > 10) { 46 | saturation = 10; 47 | } 48 | if (saturation < 6) { 49 | saturation = 6; 50 | } 51 | return Math.round(saturation); 52 | }; 53 | var getValue = function(hsv, i, isLight) { 54 | if (isLight) { 55 | return Math.round(hsv.v * 100) + brightnessStep1 * i; 56 | } 57 | return Math.round(hsv.v * 100) - brightnessStep2 * i; 58 | }; 59 | 60 | this.colorPalette = function(color, index) { 61 | var isLight = index <= 6; 62 | var hsv = tinycolor(color).toHsv(); 63 | var i = isLight ? lightColorCount + 1 - index : index - lightColorCount - 1; 64 | return tinycolor({ 65 | h: getHue(hsv, i, isLight), 66 | s: getSaturation(hsv, i, isLight), 67 | v: getValue(hsv, i, isLight), 68 | }).toHexString(); 69 | }; 70 | })()`; 71 | } 72 | // It is hacky way to make this function will be compiled preferentially by less 73 | // resolve error: `ReferenceError: colorPalette is not defined` 74 | // https://github.com/ant-design/ant-motion/issues/44 75 | .colorPaletteMixin(); 76 | -------------------------------------------------------------------------------- /test/Component/color/tinyColor.less: -------------------------------------------------------------------------------- 1 | /* stylelint-disable declaration-bang-space-before,no-duplicate-selectors,string-no-newline */ 2 | .tinyColorMixin() { 3 | @functions: ~`(function() { 4 | // TinyColor v1.4.1 5 | // https://github.com/bgrins/TinyColor 6 | // 2016-07-07, Brian Grinstead, MIT License 7 | var trimLeft = /^\s+/, 8 | trimRight = /\s+$/, 9 | tinyCounter = 0, 10 | mathRound = Math.round, 11 | mathMin = Math.min, 12 | mathMax = Math.max; 13 | 14 | function tinycolor (color, opts) { 15 | 16 | color = (color) ? color : ''; 17 | opts = opts || { }; 18 | 19 | // If input is already a tinycolor, return itself 20 | if (color instanceof tinycolor) { 21 | return color; 22 | } 23 | // If we are called as a function, call using new instead 24 | if (!(this instanceof tinycolor)) { 25 | return new tinycolor(color, opts); 26 | } 27 | 28 | var rgb = inputToRGB(color); 29 | this._originalInput = color, 30 | this._r = rgb.r, 31 | this._g = rgb.g, 32 | this._b = rgb.b, 33 | this._a = rgb.a, 34 | this._roundA = mathRound(100*this._a) / 100, 35 | this._format = opts.format || rgb.format; 36 | this._gradientType = opts.gradientType; 37 | 38 | // Don't let the range of [0,255] come back in [0,1]. 39 | // Potentially lose a little bit of precision here, but will fix issues where 40 | // .5 gets interpreted as half of the total, instead of half of 1 41 | // If it was supposed to be 128, this was already taken care of by inputToRgb 42 | if (this._r < 1) { this._r = mathRound(this._r); } 43 | if (this._g < 1) { this._g = mathRound(this._g); } 44 | if (this._b < 1) { this._b = mathRound(this._b); } 45 | 46 | this._ok = rgb.ok; 47 | this._tc_id = tinyCounter++; 48 | } 49 | 50 | tinycolor.prototype = { 51 | toHsv: function() { 52 | var hsv = rgbToHsv(this._r, this._g, this._b); 53 | return { h: hsv.h * 360, s: hsv.s, v: hsv.v, a: this._a }; 54 | }, 55 | toHex: function(allow3Char) { 56 | return rgbToHex(this._r, this._g, this._b, allow3Char); 57 | }, 58 | toHexString: function(allow3Char) { 59 | return '#' + this.toHex(allow3Char); 60 | } 61 | }; 62 | 63 | // rgbToHsv 64 | // Converts an RGB color value to HSV 65 | // *Assumes:* r, g, and b are contained in the set [0, 255] or [0, 1] 66 | // *Returns:* { h, s, v } in [0,1] 67 | function rgbToHsv(r, g, b) { 68 | 69 | r = bound01(r, 255); 70 | g = bound01(g, 255); 71 | b = bound01(b, 255); 72 | 73 | var max = mathMax(r, g, b), min = mathMin(r, g, b); 74 | var h, s, v = max; 75 | 76 | var d = max - min; 77 | s = max === 0 ? 0 : d / max; 78 | 79 | if(max == min) { 80 | h = 0; // achromatic 81 | } 82 | else { 83 | switch(max) { 84 | case r: h = (g - b) / d + (g < b ? 6 : 0); break; 85 | case g: h = (b - r) / d + 2; break; 86 | case b: h = (r - g) / d + 4; break; 87 | } 88 | h /= 6; 89 | } 90 | return { h: h, s: s, v: v }; 91 | } 92 | 93 | // rgbToHex 94 | // Converts an RGB color to hex 95 | // Assumes r, g, and b are contained in the set [0, 255] 96 | // Returns a 3 or 6 character hex 97 | function rgbToHex(r, g, b, allow3Char) { 98 | 99 | var hex = [ 100 | pad2(mathRound(r).toString(16)), 101 | pad2(mathRound(g).toString(16)), 102 | pad2(mathRound(b).toString(16)) 103 | ]; 104 | 105 | // Return a 3 character hex if possible 106 | if (allow3Char && hex[0].charAt(0) == hex[0].charAt(1) && hex[1].charAt(0) == hex[1].charAt(1) && hex[2].charAt(0) == hex[2].charAt(1)) { 107 | return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0); 108 | } 109 | 110 | return hex.join(""); 111 | } 112 | 113 | 114 | // Take input from [0, n] and return it as [0, 1] 115 | function bound01(n, max) { 116 | if (isOnePointZero(n)) { n = "100%"; } 117 | 118 | var processPercent = isPercentage(n); 119 | n = mathMin(max, mathMax(0, parseFloat(n))); 120 | 121 | // Automatically convert percentage into number 122 | if (processPercent) { 123 | n = parseInt(n * max, 10) / 100; 124 | } 125 | 126 | // Handle floating point rounding errors 127 | if ((Math.abs(n - max) < 0.000001)) { 128 | return 1; 129 | } 130 | 131 | // Convert into [0, 1] range if it isn't already 132 | return (n % max) / parseFloat(max); 133 | } 134 | 135 | // Given a string or object, convert that input to RGB 136 | // Possible string inputs: 137 | // 138 | // "red" 139 | // "#f00" or "f00" 140 | // "#ff0000" or "ff0000" 141 | // "#ff000000" or "ff000000" 142 | // "rgb 255 0 0" or "rgb (255, 0, 0)" 143 | // "rgb 1.0 0 0" or "rgb (1, 0, 0)" 144 | // "rgba (255, 0, 0, 1)" or "rgba 255, 0, 0, 1" 145 | // "rgba (1.0, 0, 0, 1)" or "rgba 1.0, 0, 0, 1" 146 | // "hsl(0, 100%, 50%)" or "hsl 0 100% 50%" 147 | // "hsla(0, 100%, 50%, 1)" or "hsla 0 100% 50%, 1" 148 | // "hsv(0, 100%, 100%)" or "hsv 0 100% 100%" 149 | // 150 | function inputToRGB(color) { 151 | 152 | var rgb = { r: 0, g: 0, b: 0 }; 153 | var a = 1; 154 | var s = null; 155 | var v = null; 156 | var l = null; 157 | var ok = false; 158 | var format = false; 159 | 160 | if (typeof color == "string") { 161 | color = stringInputToObject(color); 162 | } 163 | 164 | if (typeof color == "object") { 165 | if (isValidCSSUnit(color.r) && isValidCSSUnit(color.g) && isValidCSSUnit(color.b)) { 166 | rgb = rgbToRgb(color.r, color.g, color.b); 167 | ok = true; 168 | format = String(color.r).substr(-1) === "%" ? "prgb" : "rgb"; 169 | } 170 | else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.v)) { 171 | s = convertToPercentage(color.s); 172 | v = convertToPercentage(color.v); 173 | rgb = hsvToRgb(color.h, s, v); 174 | ok = true; 175 | format = "hsv"; 176 | } 177 | else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.l)) { 178 | s = convertToPercentage(color.s); 179 | l = convertToPercentage(color.l); 180 | rgb = hslToRgb(color.h, s, l); 181 | ok = true; 182 | format = "hsl"; 183 | } 184 | 185 | if (color.hasOwnProperty("a")) { 186 | a = color.a; 187 | } 188 | } 189 | 190 | a = boundAlpha(a); 191 | 192 | return { 193 | ok: ok, 194 | format: color.format || format, 195 | r: mathMin(255, mathMax(rgb.r, 0)), 196 | g: mathMin(255, mathMax(rgb.g, 0)), 197 | b: mathMin(255, mathMax(rgb.b, 0)), 198 | a: a 199 | }; 200 | } 201 | 202 | // Conversion Functions 203 | // -------------------- 204 | 205 | // rgbToHsl, rgbToHsv, hslToRgb, hsvToRgb modified from: 206 | // 207 | 208 | // rgbToRgb 209 | // Handle bounds / percentage checking to conform to CSS color spec 210 | // 211 | // *Assumes:* r, g, b in [0, 255] or [0, 1] 212 | // *Returns:* { r, g, b } in [0, 255] 213 | function rgbToRgb(r, g, b){ 214 | return { 215 | r: bound01(r, 255) * 255, 216 | g: bound01(g, 255) * 255, 217 | b: bound01(b, 255) * 255 218 | }; 219 | } 220 | 221 | // hslToRgb 222 | // Converts an HSL color value to RGB. 223 | // *Assumes:* h is contained in [0, 1] or [0, 360] and s and l are contained [0, 1] or [0, 100] 224 | // *Returns:* { r, g, b } in the set [0, 255] 225 | function hslToRgb(h, s, l) { 226 | var r, g, b; 227 | 228 | h = bound01(h, 360); 229 | s = bound01(s, 100); 230 | l = bound01(l, 100); 231 | 232 | function hue2rgb(p, q, t) { 233 | if(t < 0) t += 1; 234 | if(t > 1) t -= 1; 235 | if(t < 1/6) return p + (q - p) * 6 * t; 236 | if(t < 1/2) return q; 237 | if(t < 2/3) return p + (q - p) * (2/3 - t) * 6; 238 | return p; 239 | } 240 | 241 | if(s === 0) { 242 | r = g = b = l; // achromatic 243 | } 244 | else { 245 | var q = l < 0.5 ? l * (1 + s) : l + s - l * s; 246 | var p = 2 * l - q; 247 | r = hue2rgb(p, q, h + 1/3); 248 | g = hue2rgb(p, q, h); 249 | b = hue2rgb(p, q, h - 1/3); 250 | } 251 | 252 | return { r: r * 255, g: g * 255, b: b * 255 }; 253 | } 254 | 255 | // hsvToRgb 256 | // Converts an HSV color value to RGB. 257 | // *Assumes:* h is contained in [0, 1] or [0, 360] and s and v are contained in [0, 1] or [0, 100] 258 | // *Returns:* { r, g, b } in the set [0, 255] 259 | function hsvToRgb(h, s, v) { 260 | 261 | h = bound01(h, 360) * 6; 262 | s = bound01(s, 100); 263 | v = bound01(v, 100); 264 | 265 | var i = Math.floor(h), 266 | f = h - i, 267 | p = v * (1 - s), 268 | q = v * (1 - f * s), 269 | t = v * (1 - (1 - f) * s), 270 | mod = i % 6, 271 | r = [v, q, p, p, t, v][mod], 272 | g = [t, v, v, q, p, p][mod], 273 | b = [p, p, t, v, v, q][mod]; 274 | 275 | return { r: r * 255, g: g * 255, b: b * 255 }; 276 | } 277 | 278 | // Big List of Colors 279 | // ------------------ 280 | // 281 | var names = tinycolor.names = { 282 | aliceblue: "f0f8ff", 283 | antiquewhite: "faebd7", 284 | aqua: "0ff", 285 | aquamarine: "7fffd4", 286 | azure: "f0ffff", 287 | beige: "f5f5dc", 288 | bisque: "ffe4c4", 289 | black: "000", 290 | blanchedalmond: "ffebcd", 291 | blue: "00f", 292 | blueviolet: "8a2be2", 293 | brown: "a52a2a", 294 | burlywood: "deb887", 295 | burntsienna: "ea7e5d", 296 | cadetblue: "5f9ea0", 297 | chartreuse: "7fff00", 298 | chocolate: "d2691e", 299 | coral: "ff7f50", 300 | cornflowerblue: "6495ed", 301 | cornsilk: "fff8dc", 302 | crimson: "dc143c", 303 | cyan: "0ff", 304 | darkblue: "00008b", 305 | darkcyan: "008b8b", 306 | darkgoldenrod: "b8860b", 307 | darkgray: "a9a9a9", 308 | darkgreen: "006400", 309 | darkgrey: "a9a9a9", 310 | darkkhaki: "bdb76b", 311 | darkmagenta: "8b008b", 312 | darkolivegreen: "556b2f", 313 | darkorange: "ff8c00", 314 | darkorchid: "9932cc", 315 | darkred: "8b0000", 316 | darksalmon: "e9967a", 317 | darkseagreen: "8fbc8f", 318 | darkslateblue: "483d8b", 319 | darkslategray: "2f4f4f", 320 | darkslategrey: "2f4f4f", 321 | darkturquoise: "00ced1", 322 | darkviolet: "9400d3", 323 | deeppink: "ff1493", 324 | deepskyblue: "00bfff", 325 | dimgray: "696969", 326 | dimgrey: "696969", 327 | dodgerblue: "1e90ff", 328 | firebrick: "b22222", 329 | floralwhite: "fffaf0", 330 | forestgreen: "228b22", 331 | fuchsia: "f0f", 332 | gainsboro: "dcdcdc", 333 | ghostwhite: "f8f8ff", 334 | gold: "ffd700", 335 | goldenrod: "daa520", 336 | gray: "808080", 337 | green: "008000", 338 | greenyellow: "adff2f", 339 | grey: "808080", 340 | honeydew: "f0fff0", 341 | hotpink: "ff69b4", 342 | indianred: "cd5c5c", 343 | indigo: "4b0082", 344 | ivory: "fffff0", 345 | khaki: "f0e68c", 346 | lavender: "e6e6fa", 347 | lavenderblush: "fff0f5", 348 | lawngreen: "7cfc00", 349 | lemonchiffon: "fffacd", 350 | lightblue: "add8e6", 351 | lightcoral: "f08080", 352 | lightcyan: "e0ffff", 353 | lightgoldenrodyellow: "fafad2", 354 | lightgray: "d3d3d3", 355 | lightgreen: "90ee90", 356 | lightgrey: "d3d3d3", 357 | lightpink: "ffb6c1", 358 | lightsalmon: "ffa07a", 359 | lightseagreen: "20b2aa", 360 | lightskyblue: "87cefa", 361 | lightslategray: "789", 362 | lightslategrey: "789", 363 | lightsteelblue: "b0c4de", 364 | lightyellow: "ffffe0", 365 | lime: "0f0", 366 | limegreen: "32cd32", 367 | linen: "faf0e6", 368 | magenta: "f0f", 369 | maroon: "800000", 370 | mediumaquamarine: "66cdaa", 371 | mediumblue: "0000cd", 372 | mediumorchid: "ba55d3", 373 | mediumpurple: "9370db", 374 | mediumseagreen: "3cb371", 375 | mediumslateblue: "7b68ee", 376 | mediumspringgreen: "00fa9a", 377 | mediumturquoise: "48d1cc", 378 | mediumvioletred: "c71585", 379 | midnightblue: "191970", 380 | mintcream: "f5fffa", 381 | mistyrose: "ffe4e1", 382 | moccasin: "ffe4b5", 383 | navajowhite: "ffdead", 384 | navy: "000080", 385 | oldlace: "fdf5e6", 386 | olive: "808000", 387 | olivedrab: "6b8e23", 388 | orange: "ffa500", 389 | orangered: "ff4500", 390 | orchid: "da70d6", 391 | palegoldenrod: "eee8aa", 392 | palegreen: "98fb98", 393 | paleturquoise: "afeeee", 394 | palevioletred: "db7093", 395 | papayawhip: "ffefd5", 396 | peachpuff: "ffdab9", 397 | peru: "cd853f", 398 | pink: "ffc0cb", 399 | plum: "dda0dd", 400 | powderblue: "b0e0e6", 401 | purple: "800080", 402 | rebeccapurple: "663399", 403 | red: "f00", 404 | rosybrown: "bc8f8f", 405 | royalblue: "4169e1", 406 | saddlebrown: "8b4513", 407 | salmon: "fa8072", 408 | sandybrown: "f4a460", 409 | seagreen: "2e8b57", 410 | seashell: "fff5ee", 411 | sienna: "a0522d", 412 | silver: "c0c0c0", 413 | skyblue: "87ceeb", 414 | slateblue: "6a5acd", 415 | slategray: "708090", 416 | slategrey: "708090", 417 | snow: "fffafa", 418 | springgreen: "00ff7f", 419 | steelblue: "4682b4", 420 | tan: "d2b48c", 421 | teal: "008080", 422 | thistle: "d8bfd8", 423 | tomato: "ff6347", 424 | turquoise: "40e0d0", 425 | violet: "ee82ee", 426 | wheat: "f5deb3", 427 | white: "fff", 428 | whitesmoke: "f5f5f5", 429 | yellow: "ff0", 430 | yellowgreen: "9acd32" 431 | }; 432 | 433 | // Utilities 434 | // --------- 435 | 436 | // Return a valid alpha value [0,1] with all invalid values being set to 1 437 | function boundAlpha(a) { 438 | a = parseFloat(a); 439 | 440 | if (isNaN(a) || a < 0 || a > 1) { 441 | a = 1; 442 | } 443 | 444 | return a; 445 | } 446 | 447 | // Parse a base-16 hex value into a base-10 integer 448 | function parseIntFromHex(val) { 449 | return parseInt(val, 16); 450 | } 451 | 452 | // Converts a hex value to a decimal 453 | function convertHexToDecimal(h) { 454 | return (parseIntFromHex(h) / 255); 455 | } 456 | 457 | // Need to handle 1.0 as 100%, since once it is a number, there is no difference between it and 1 458 | // 459 | function isOnePointZero(n) { 460 | return typeof n == "string" && n.indexOf('.') != -1 && parseFloat(n) === 1; 461 | } 462 | 463 | // Check to see if string passed in is a percentage 464 | function isPercentage(n) { 465 | return typeof n === "string" && n.indexOf('%') != -1; 466 | } 467 | 468 | // Force a hex value to have 2 characters 469 | function pad2(c) { 470 | return c.length == 1 ? '0' + c : '' + c; 471 | } 472 | 473 | // Replace a decimal with it's percentage value 474 | function convertToPercentage(n) { 475 | if (n <= 1) { 476 | n = (n * 100) + "%"; 477 | } 478 | 479 | return n; 480 | } 481 | 482 | var matchers = (function() { 483 | 484 | // 485 | var CSS_INTEGER = "[-\\+]?\\d+%?"; 486 | 487 | // 488 | var CSS_NUMBER = "[-\\+]?\\d*\\.\\d+%?"; 489 | 490 | // Allow positive/negative integer/number. Don't capture the either/or, just the entire outcome. 491 | var CSS_UNIT = "(?:" + CSS_NUMBER + ")|(?:" + CSS_INTEGER + ")"; 492 | 493 | // Actual matching. 494 | // Parentheses and commas are optional, but not required. 495 | // Whitespace can take the place of commas or opening paren 496 | var PERMISSIVE_MATCH3 = "[\\s|\\(]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")\\s*\\)?"; 497 | var PERMISSIVE_MATCH4 = "[\\s|\\(]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")\\s*\\)?"; 498 | 499 | return { 500 | CSS_UNIT: new RegExp(CSS_UNIT), 501 | rgb: new RegExp("rgb" + PERMISSIVE_MATCH3), 502 | rgba: new RegExp("rgba" + PERMISSIVE_MATCH4), 503 | hsl: new RegExp("hsl" + PERMISSIVE_MATCH3), 504 | hsla: new RegExp("hsla" + PERMISSIVE_MATCH4), 505 | hsv: new RegExp("hsv" + PERMISSIVE_MATCH3), 506 | hsva: new RegExp("hsva" + PERMISSIVE_MATCH4), 507 | hex3: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/, 508 | hex6: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/, 509 | hex4: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/, 510 | hex8: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/ 511 | }; 512 | })(); 513 | 514 | // isValidCSSUnit 515 | // Take in a single string / number and check to see if it looks like a CSS unit 516 | // (see matchers above for definition). 517 | function isValidCSSUnit(color) { 518 | return !!matchers.CSS_UNIT.exec(color); 519 | } 520 | 521 | // stringInputToObject 522 | // Permissive string parsing. Take in a number of formats, and output an object 523 | // based on detected format. Returns { r, g, b } or { h, s, l } or { h, s, v} 524 | function stringInputToObject(color) { 525 | 526 | color = color.replace(trimLeft, '').replace(trimRight, '').toLowerCase(); 527 | var named = false; 528 | if (names[color]) { 529 | color = names[color]; 530 | named = true; 531 | } 532 | else if (color == 'transparent') { 533 | return { r: 0, g: 0, b: 0, a: 0, format: "name" }; 534 | } 535 | 536 | // Try to match string input using regular expressions. 537 | // Keep most of the number bounding out of this function - don't worry about [0,1] or [0,100] or [0,360] 538 | // Just return an object and let the conversion functions handle that. 539 | // This way the result will be the same whether the tinycolor is initialized with string or object. 540 | var match; 541 | /* eslint-disable no-cond-assign */ 542 | if ((match = matchers.rgb.exec(color))) { 543 | return { r: match[1], g: match[2], b: match[3] }; 544 | } 545 | if ((match = matchers.rgba.exec(color))) { 546 | return { r: match[1], g: match[2], b: match[3], a: match[4] }; 547 | } 548 | if ((match = matchers.hsl.exec(color))) { 549 | return { h: match[1], s: match[2], l: match[3] }; 550 | } 551 | if ((match = matchers.hsla.exec(color))) { 552 | return { h: match[1], s: match[2], l: match[3], a: match[4] }; 553 | } 554 | if ((match = matchers.hsv.exec(color))) { 555 | return { h: match[1], s: match[2], v: match[3] }; 556 | } 557 | if ((match = matchers.hsva.exec(color))) { 558 | return { h: match[1], s: match[2], v: match[3], a: match[4] }; 559 | } 560 | if ((match = matchers.hex8.exec(color))) { 561 | return { 562 | r: parseIntFromHex(match[1]), 563 | g: parseIntFromHex(match[2]), 564 | b: parseIntFromHex(match[3]), 565 | a: convertHexToDecimal(match[4]), 566 | format: named ? "name" : "hex8" 567 | }; 568 | } 569 | if ((match = matchers.hex6.exec(color))) { 570 | return { 571 | r: parseIntFromHex(match[1]), 572 | g: parseIntFromHex(match[2]), 573 | b: parseIntFromHex(match[3]), 574 | format: named ? "name" : "hex" 575 | }; 576 | } 577 | if ((match = matchers.hex4.exec(color))) { 578 | return { 579 | r: parseIntFromHex(match[1] + '' + match[1]), 580 | g: parseIntFromHex(match[2] + '' + match[2]), 581 | b: parseIntFromHex(match[3] + '' + match[3]), 582 | a: convertHexToDecimal(match[4] + '' + match[4]), 583 | format: named ? "name" : "hex8" 584 | }; 585 | } 586 | if ((match = matchers.hex3.exec(color))) { 587 | return { 588 | r: parseIntFromHex(match[1] + '' + match[1]), 589 | g: parseIntFromHex(match[2] + '' + match[2]), 590 | b: parseIntFromHex(match[3] + '' + match[3]), 591 | format: named ? "name" : "hex" 592 | }; 593 | } 594 | /* eslint-enable no-cond-assign */ 595 | 596 | return false; 597 | } 598 | 599 | this.tinycolor = tinycolor; 600 | 601 | })()`; 602 | } 603 | // It is hacky way to make this function will be compiled preferentially by less 604 | // resolve error: `ReferenceError: colorPalette is not defined` 605 | // https://github.com/ant-design/ant-motion/issues/44 606 | .tinyColorMixin(); 607 | -------------------------------------------------------------------------------- /test/Component/createGlobalStyle.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import renderer from 'react-test-renderer'; 3 | import {createGlobalStyle} from 'styled-components'; 4 | import 'jest-styled-components'; 5 | 6 | export const getCSS = scope => Array.from(scope.querySelectorAll('style')).map(tag => tag.innerHTML).join('\n'); 7 | 8 | test('Should theme the global style', () => { 9 | const GlobalStyle = createGlobalStyle` 10 | html { 11 | color: @color; 12 | } 13 | `; 14 | renderer.create(); 15 | expect(getCSS(document)).toMatchSnapshot(); 16 | }); -------------------------------------------------------------------------------- /test/Component/echoColor.less: -------------------------------------------------------------------------------- 1 | /* stylelint-disable declaration-bang-space-before,no-duplicate-selectors */ 2 | 3 | .echoColorMixin() { 4 | @functions: ~`(function() { 5 | this.echoColor = function(color, index) { 6 | return color; 7 | } 8 | } 9 | )()`; 10 | } 11 | 12 | // It is hacky way to make this function will be compiled preferentially by less 13 | .echoColorMixin(); 14 | -------------------------------------------------------------------------------- /test/Component/import-scoped.less: -------------------------------------------------------------------------------- 1 | @import "~@scoped/package/import.less"; -------------------------------------------------------------------------------- /test/Component/import.less: -------------------------------------------------------------------------------- 1 | .foo { 2 | background: #900; 3 | border: 1px solid @color; 4 | } 5 | 6 | @color: green; 7 | 8 | @font-family-no-number: "Chinese Quote", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", Helvetica, Arial, sans-serif; 9 | @font-family: "Monospaced Number less", @font-family-no-number; -------------------------------------------------------------------------------- /test/Component/javascript.less: -------------------------------------------------------------------------------- 1 | /* stylelint-disable declaration-bang-space-before,no-duplicate-selectors */ 2 | 3 | .echoColor() { 4 | @functions: ~`(function(colorStr) { 5 | return colorStr; 6 | } 7 | )()`; 8 | } 9 | 10 | // It is hacky way to make this function will be compiled preferentially by less 11 | .echoColor(); 12 | 13 | 14 | @import 'color/colorPalette'; 15 | 16 | // color palettes 17 | @blue-5: ~`colorPalette("@{blue-6}", 5)`; 18 | @blue-6: #1890ff; 19 | 20 | @primary-color: @blue-6; 21 | @primary-1: ~`colorPalette("@{primary-color}", 1)`; 22 | @primary-2: color(~`colorPalette("@{primary-color}", 1)`); 23 | -------------------------------------------------------------------------------- /test/packages/scoped-package/import.less: -------------------------------------------------------------------------------- 1 | @foo: yellow; -------------------------------------------------------------------------------- /test/packages/scoped-package/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@scoped/package", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT" 6 | } 7 | --------------------------------------------------------------------------------