├── .babelrc
├── .codeclimate.yml
├── .eslintignore
├── .eslintrc
├── .gitignore
├── .jshintrc
├── .prettierrc
├── .travis.yml
├── .vscode
└── settings.json
├── Changelog.md
├── LICENSE
├── README.md
├── modules
├── __tests__
│ ├── assignStyle-test.ts
│ ├── camelCaseProperty-test.ts
│ ├── cssifyDeclaration-test.ts
│ ├── cssifyObject-test.ts
│ ├── isPrefixedProperty-test.ts
│ ├── isPrefixedValue-test.ts
│ ├── isUnitlessProperty-test.ts
│ ├── normalizeProperty-test.ts
│ ├── resolveArrayValue-test.ts
│ ├── unprefixProperty-test.ts
│ └── unprefixValue-test.ts
├── assignStyle.ts
├── camelCaseProperty.ts
├── cssifyDeclaration.ts
├── cssifyObject.ts
├── hyphenate-style-name.d.ts
├── hyphenateProperty.ts
├── index.ts
├── isPrefixedProperty.ts
├── isPrefixedValue.ts
├── isUnitlessProperty.ts
├── normalizeProperty.ts
├── resolveArrayValue.ts
├── unprefixProperty.ts
└── unprefixValue.ts
├── package.json
├── tsconfig.json
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "@babel/preset-env",
5 | {
6 | "modules": false
7 | }
8 | ],
9 | "@babel/typescript"
10 | ],
11 | "env": {
12 | "commonjs": {
13 | "plugins": ["transform-es2015-modules-commonjs"]
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/.codeclimate.yml:
--------------------------------------------------------------------------------
1 | languages:
2 | Ruby: true
3 | JavaScript: true
4 | PHP: true
5 | Python: true
6 | exclude_paths:
7 | - "dist/*"
8 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | modules/coverage/**
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "@typescript-eslint/parser",
3 | "extends": ["airbnb-base"],
4 | "plugins": ["@typescript-eslint"],
5 | "env": {
6 | "browser": true,
7 | "node": true,
8 | "jest": true
9 | },
10 | "rules": {
11 | "semi": [2, "never"],
12 | "object-curly-newline": [0],
13 | "object-property-newline": [0],
14 | "comma-dangle": [0],
15 | "prefer-template": [0],
16 | "import/extensions": [0], // handled by TS
17 | "import/no-extraneous-dependencies": [0],
18 | "import/no-unresolved": [0], // handled by TS
19 | "react/prop-types": [0],
20 | "no-confusing-arrow": [0],
21 | "no-underscore-dangle": [0],
22 | "no-unused-vars": [0], // handled by TS
23 | "no-param-reassign": [0],
24 | "no-plusplus": [0],
25 | "guard-for-in": [0],
26 | "no-restricted-syntax": [0],
27 | "no-continue": [1],
28 | "no-prototype-builtins": [0],
29 | "max-len": [0, 80],
30 | "no-mixed-operators": [0],
31 | "no-lonely-if": [1],
32 | "no-bitwise": [0],
33 | "arrow-parens": [0],
34 | "operator-linebreak": [0] // handled by Prettier
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # OS or Editor files
2 | ._*
3 | .DS_Store
4 | Thumbs.db
5 |
6 | # Files that might appear on external disks
7 | .Spotlight-V100
8 | .Trashes
9 |
10 | # Always-ignore extensions
11 | *~
12 | *.diff
13 | *.err
14 | *.log
15 | *.orig
16 | *.pyc
17 | *.rej
18 | *.sass-cache
19 | *.sw?
20 | *.vi
21 |
22 |
23 | node_modules
24 | es
25 | coverage
26 | lib
27 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "esnext": true,
3 | "asi": true
4 | }
5 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "es5",
3 | "singleQuote": true,
4 | "bracketSpacing": true,
5 | "jsxBracketSameLine": true,
6 | "printWidth": 80,
7 | "tabWidth": 2,
8 | "useTabs": false,
9 | "semi": false
10 | }
11 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "5"
4 | script:
5 | - npm run check
6 | addons:
7 | code_climate:
8 | repo_token: 354c5092a67b6e59c6e0a0081e306ab5934cf4e8860332c56d93538966809780
9 | after_script:
10 | - codeclimate-test-reporter < coverage/lcov.info
11 | notifications:
12 | email: false
13 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "files.exclude": {
3 | "**/._*": true,
4 | "**/.DS_Store": true,
5 | "**/Thumbs.db": true,
6 | "**/.Spotlight-V100": true,
7 | "**/.Trashes": true,
8 | "**/*~": true,
9 | "**/*.diff": true,
10 | "**/*.err": true,
11 | "**/*.log": true,
12 | "**/*.orig": true,
13 | "**/*.pyc": true,
14 | "**/*.rej": true,
15 | "**/*.sass-cache": true,
16 | "**/*.sw?": true,
17 | "**/*.vi": true,
18 | "**/node_modules": true,
19 | "**/es": true,
20 | "**/coverage": true,
21 | "**/lib": true
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Changelog.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ### 3.1.0
4 |
5 | - added a cache to improve performance for recuring properties
6 | - added TypeScript support
7 |
8 | ### 3.0.2
9 |
10 | - added new unitless properties
11 |
12 | ### 3.0.0
13 |
14 | - `assignStyle` now correctly merges array values without duplicates where the last occurence always wins in order
15 |
16 | ### 2.0.0
17 |
18 | - improve `assignStyle` to replace arrays
19 |
20 | ### 1.0.3
21 |
22 | - performance improvements
23 |
24 | ### 1.0.2
25 |
26 | - added `resolveArrayValue` and `assignStyle`
27 |
28 | ### 1.0.1
29 |
30 | - added `cssifyDeclaration` and `cssifyObject`
31 |
32 | ### 1.0.0
33 |
34 | Initial version
35 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Robin Frischmann
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 | # CSS-in-JS Utilities
2 | A library that provides useful utilities functions for CSS-in-JS solutions.
3 | They are intended to be used by CSS-in-JS library authors rather used directly.
4 |
5 |
6 |
7 |
8 | ## Installation
9 | ```sh
10 | yarn add css-in-js-utils
11 | ```
12 |
13 | ## Why?
14 | By now I have authored and collaborated on many different libraries and found I would rewrite the very same utility functions every time. That's why this repository is hosting small utilities especially built for CSS-in-JS solutions and tools. Even if there are tons of different libraries already, they all basically use the same mechanisms and utilities.
15 |
16 | ## Utilities
17 | * [`assignStyle(base, ...extend)`](#assignstylebase-extend)
18 | * [`camelCaseProperty(property)`](#camelcasepropertyproperty)
19 | * [`cssifyDeclaration(property, value)`](#cssifydeclarationproperty-value)
20 | * [`cssifyObject(object)`](#cssifyobjectobject)
21 | * [`hyphenateProperty(property)`](#hyphenatepropertyproperty)
22 | * [`isPrefixedProperty(property)`](#isprefixedpropertyproperty)
23 | * [`isPrefixedValue(value)`](#isprefixedvaluevalue)
24 | * [`isUnitlessProperty(property)`](#isunitlesspropertyproperty)
25 | * [`normalizeProperty(property)`](#normalizepropertyproperty)
26 | * [`resolveArrayValue(property, value)`](#resolvearrayvalueproperty-value)
27 | * [`unprefixProperty(property)`](#unprefixpropertyproperty)
28 | * [`unprefixValue(value)`](#unprefixvaluevalue)
29 |
30 | ------
31 |
32 | ### `assignStyle(base, ...extend)`
33 | Merges deep style objects similar to `Object.assign`.
34 | It also merges array values into a single array whithout creating duplicates. The last occurence of every item wins.
35 |
36 | ```javascript
37 | import { assignStyle } from 'css-in-js-utils'
38 |
39 | assignStyle(
40 | { color: 'red', backgroundColor: 'black' },
41 | { color: 'blue' }
42 | )
43 | // => { color: 'blue', backgroundColor: 'black' }
44 |
45 | assignStyle(
46 | {
47 | color: 'red',
48 | ':hover': {
49 | backgroundColor: 'black'
50 | }
51 | },
52 | {
53 | ':hover': {
54 | backgroundColor: 'blue'
55 | }
56 | }
57 | )
58 | // => { color: 'red', ':hover': { backgroundColor: 'blue' }}
59 | ```
60 |
61 | ------
62 |
63 | ### `camelCaseProperty(property)`
64 | Converts the `property` to camelCase.
65 |
66 | ```javascript
67 | import { camelCaseProperty } from 'css-in-js-utils'
68 |
69 | camelCaseProperty('padding-top')
70 | // => 'paddingTop'
71 |
72 | camelCaseProperty('-webkit-transition')
73 | // => 'WebkitTransition'
74 | ```
75 |
76 | ------
77 |
78 | ### `cssifyDeclaration(property, value)`
79 | Generates a CSS declaration (`property`:`value`) string.
80 |
81 | ```javascript
82 | import { cssifyDeclaration } from 'css-in-js-utils'
83 |
84 | cssifyDeclaration('paddingTop', '400px')
85 | // => 'padding-top:400px'
86 |
87 | cssifyDeclaration('WebkitFlex', 3)
88 | // => '-webkit-flex:3'
89 | ```
90 |
91 | ------
92 |
93 | ### `cssifyObject(object)`
94 | Generates a CSS string using all key-property pairs in `object`.
95 | It automatically removes declarations with value types other than `number` and `string`.
96 |
97 | ```javascript
98 | import { cssifyObject } from 'css-in-js-utils'
99 |
100 | cssifyObject({
101 | paddingTop: '400px',
102 | paddingBottom: undefined,
103 | WebkitFlex: 3,
104 | _anyKey: [1, 2, 4]
105 | })
106 | // => 'padding-top:400px;-webkit-flex:3'
107 | ```
108 |
109 | ------
110 |
111 | ### `hyphenateProperty(property)`
112 | Converts the `property` to hyphen-case.
113 | > Directly mirrors [hyphenate-style-name](https://github.com/rexxars/hyphenate-style-name).
114 |
115 | ```javascript
116 | import { hyphenateProperty } from 'css-in-js-utils'
117 |
118 | hyphenateProperty('paddingTop')
119 | // => 'padding-top'
120 |
121 | hyphenateProperty('WebkitTransition')
122 | // => '-webkit-transition'
123 | ```
124 |
125 | ------
126 |
127 | ### `isPrefixedProperty(property)`
128 | Checks if a `property` includes a vendor prefix.
129 |
130 | ```javascript
131 | import { isPrefixedProperty } from 'css-in-js-utils'
132 |
133 | isPrefixedProperty('paddingTop')
134 | // => false
135 |
136 | isPrefixedProperty('WebkitTransition')
137 | // => true
138 | ```
139 |
140 | ------
141 | ### `isPrefixedValue(value)`
142 | Checks if a `value` includes vendor prefixes.
143 |
144 | ```javascript
145 | import { isPrefixedValue } from 'css-in-js-utils'
146 |
147 | isPrefixedValue('200px')
148 | isPrefixedValue(200)
149 | // => false
150 |
151 | isPrefixedValue('-webkit-calc(100% - 50px)')
152 | // => true
153 | ```
154 |
155 | ------
156 |
157 | ### `isUnitlessProperty(property)`
158 | Checks if a `property` accepts unitless values.
159 |
160 | ```javascript
161 | import { isUnitlessProperty } from 'css-in-js-utils'
162 |
163 | isUnitlessProperty('width')
164 | // => false
165 |
166 | isUnitlessProperty('flexGrow')
167 | isUnitlessProperty('lineHeight')
168 | isUnitlessProperty('line-height')
169 | // => true
170 | ```
171 |
172 | ------
173 |
174 | ### `normalizeProperty(property)`
175 | Normalizes the `property` by unprefixing **and** camelCasing it.
176 | > Uses the [`camelCaseProperty`](#camelcasepropertyproperty) and [`unprefixProperty`](#unprefixpropertyproperty)-methods.
177 |
178 | ```javascript
179 | import { normalizeProperty } from 'css-in-js-utils'
180 |
181 | normalizeProperty('-webkit-transition-delay')
182 | // => 'transitionDelay'
183 | ```
184 |
185 | ------
186 |
187 | ### `resolveArrayValue(property, value)`
188 | Concatenates array values to single CSS value.
189 | > Uses the [`hyphenateProperty`](#hyphenatepropertyproperty)-method.
190 |
191 |
192 | ```javascript
193 | import { resolveArrayValue } from 'css-in-js-utils'
194 |
195 | resolveArrayValue('display', [ '-webkit-flex', 'flex' ])
196 | // => '-webkit-flex;display:flex'
197 |
198 | resolveArrayValue('paddingTop', [ 'calc(100% - 50px)', '100px' ])
199 | // => 'calc(100% - 50px);padding-top:100px'
200 | ```
201 |
202 | ------
203 |
204 | ### `unprefixProperty(property)`
205 | Removes the vendor prefix (if set) from the `property`.
206 |
207 | ```javascript
208 | import { unprefixProperty } from 'css-in-js-utils'
209 |
210 | unprefixProperty('WebkitTransition')
211 | // => 'transition'
212 |
213 | unprefixProperty('transitionDelay')
214 | // => 'transitionDelay'
215 | ```
216 |
217 | ------
218 |
219 | ### `unprefixValue(value)`
220 | Removes all vendor prefixes (if any) from the `value`.
221 |
222 | ```javascript
223 | import { unprefixValue } from 'css-in-js-utils'
224 |
225 | unprefixValue('-webkit-calc(-moz-calc(100% - 50px)/2)')
226 | // => 'calc(calc(100% - 50px)/2)'
227 |
228 | unprefixValue('100px')
229 | // => '100px'
230 | ```
231 |
232 | ## Direct Import
233 | Every utility function may be imported directly to save bundle size.
234 |
235 | ```javascript
236 | import camelCaseProperty from 'css-in-js-utils/lib/camelCaseProperty'
237 | ```
238 |
239 | ## License
240 | css-in-js-utils is licensed under the [MIT License](http://opensource.org/licenses/MIT).
241 | Documentation is licensed under [Creative Common License](http://creativecommons.org/licenses/by/4.0/).
242 | Created with ♥ by [@rofrischmann](http://rofrischmann.de).
243 |
--------------------------------------------------------------------------------
/modules/__tests__/assignStyle-test.ts:
--------------------------------------------------------------------------------
1 | import assignStyle from '../assignStyle'
2 |
3 | describe('Assinging styles', () => {
4 | it('should merge properties', () => {
5 | expect(
6 | assignStyle({ color: 'red' }, { fontSize: 12 }, { lineHeight: 1 })
7 | ).toEqual({
8 | color: 'red',
9 | fontSize: 12,
10 | lineHeight: 1,
11 | })
12 | })
13 |
14 | it('should overwrite properties from right to left', () => {
15 | expect(
16 | assignStyle({ fontSize: 12 }, { fontSize: 16 }, { fontSize: 11 })
17 | ).toEqual({ fontSize: 11 })
18 | })
19 |
20 | it('should merge nested objects', () => {
21 | expect(
22 | assignStyle(
23 | {
24 | fontSize: 12,
25 | ob2: { color: 'red' },
26 | ob3: { color: 'red' },
27 | },
28 | {
29 | fontSize: 16,
30 | ob2: { fontSize: 12 },
31 | },
32 | {
33 | fontSize: 11,
34 | ob3: { color: 'blue' },
35 | }
36 | )
37 | ).toEqual({
38 | fontSize: 11,
39 | ob2: {
40 | color: 'red',
41 | fontSize: 12,
42 | },
43 | ob3: { color: 'blue' },
44 | })
45 | })
46 |
47 | it('should not overwrite objects other than the first one', () => {
48 | const ob1 = { color: 'red' }
49 | const ob2 = { fontSize: 12 }
50 |
51 | const newOb = assignStyle({}, ob1, ob2)
52 |
53 | expect(newOb).toEqual({
54 | color: 'red',
55 | fontSize: 12,
56 | })
57 |
58 | newOb.foo = 'bar'
59 | expect(ob1).toEqual({ color: 'red' })
60 | expect(ob2).toEqual({ fontSize: 12 })
61 | })
62 |
63 | it('should use the first object as base', () => {
64 | const ob1 = { color: 'red' }
65 | const ob2 = { fontSize: 12 }
66 |
67 | const newOb = assignStyle(ob1, ob2)
68 |
69 | expect(newOb).toEqual({
70 | color: 'red',
71 | fontSize: 12,
72 | })
73 |
74 | expect(ob1).toEqual(newOb)
75 |
76 | newOb.foo = 'bar'
77 | expect(ob1).toEqual({
78 | color: 'red',
79 | fontSize: 12,
80 | foo: 'bar',
81 | })
82 | })
83 |
84 | it('should not recursively call assignStyle for null values', () => {
85 | const ob1 = { fontSize: 10 }
86 | const ob2 = { margin: null }
87 |
88 | const newOb = assignStyle({}, ob1, ob2)
89 |
90 | expect(newOb).toEqual({
91 | fontSize: 10,
92 | margin: null,
93 | })
94 | })
95 |
96 | it('should merge array values (array-single)', () => {
97 | const ob1 = { fontSize: ['10px', '10rem'] }
98 | const ob2 = { fontSize: 20 }
99 |
100 | const newOb = assignStyle({}, ob1, ob2)
101 |
102 | expect(newOb).toEqual({ fontSize: ['10px', '10rem', 20] })
103 | })
104 |
105 | it('should merge array values (single-array)', () => {
106 | const ob1 = { fontSize: 10 }
107 | const ob2 = { fontSize: ['10px', '20vw'] }
108 |
109 | const newOb = assignStyle({}, ob1, ob2)
110 |
111 | expect(newOb).toEqual({ fontSize: [10, '10px', '20vw'] })
112 | })
113 |
114 | it('should merge array values (array-array)', () => {
115 | const ob1 = { fontSize: ['20pt', 10] }
116 | const ob2 = { fontSize: ['10px', '20vw'] }
117 |
118 | const newOb = assignStyle({}, ob1, ob2)
119 |
120 | expect(newOb).toEqual({ fontSize: ['20pt', 10, '10px', '20vw'] })
121 | })
122 |
123 | it('should merge array values without duplicates (array-single)', () => {
124 | const ob1 = { fontSize: ['10px', '10rem'] }
125 | const ob2 = { fontSize: '10px' }
126 |
127 | const newOb = assignStyle({}, ob1, ob2)
128 |
129 | expect(newOb).toEqual({ fontSize: ['10rem', '10px'] })
130 | })
131 |
132 | it('should merge array values without duplicates (array-array)', () => {
133 | const ob1 = { fontSize: ['20px', '10rem', '10px'] }
134 | const ob2 = { fontSize: ['10px', 5, '10rem'] }
135 |
136 | const newOb = assignStyle({}, ob1, ob2)
137 |
138 | expect(newOb).toEqual({ fontSize: ['20px', '10px', 5, '10rem'] })
139 | })
140 |
141 | it('should merge array values without duplicates (single-array)', () => {
142 | const ob1 = { fontSize: '10px' }
143 | const ob2 = { fontSize: ['10rem', '10px'] }
144 |
145 | const newOb = assignStyle({}, ob1, ob2)
146 |
147 | expect(newOb).toEqual({ fontSize: ['10rem', '10px'] })
148 | })
149 |
150 | it('should not recursively call assignStyle for null values', () => {
151 | const ob1 = { fontSize: 10 }
152 | const ob2 = { margin: null }
153 |
154 | const newOb = assignStyle({}, ob1, ob2)
155 |
156 | expect(newOb).toEqual({
157 | fontSize: 10,
158 | margin: null,
159 | })
160 | })
161 | })
162 |
--------------------------------------------------------------------------------
/modules/__tests__/camelCaseProperty-test.ts:
--------------------------------------------------------------------------------
1 | import camelCaseProperty from '../camelCaseProperty'
2 |
3 | describe('Camel casing properties', () => {
4 | it('should camel case properties', () => {
5 | expect(camelCaseProperty('transition-delay')).toEqual('transitionDelay')
6 | expect(camelCaseProperty('-webkit-transition-delay')).toEqual(
7 | 'WebkitTransitionDelay'
8 | )
9 | expect(camelCaseProperty('-ms-transition')).toEqual('msTransition')
10 | })
11 | it('should return same output on same input', () => {
12 | expect(camelCaseProperty('border-color')).toEqual('borderColor')
13 | expect(camelCaseProperty('border-color')).toEqual('borderColor')
14 | })
15 | })
16 |
--------------------------------------------------------------------------------
/modules/__tests__/cssifyDeclaration-test.ts:
--------------------------------------------------------------------------------
1 | import cssifyDeclaration from '../cssifyDeclaration'
2 |
3 | describe('Cssifying declarations', () => {
4 | it('should return a valid css declaration', () => {
5 | expect(cssifyDeclaration('width', '300px')).toEqual('width:300px')
6 | expect(cssifyDeclaration('WebkitFlex', '1')).toEqual('-webkit-flex:1')
7 | expect(cssifyDeclaration('msTransitionDuration', '3s')).toEqual(
8 | '-ms-transition-duration:3s'
9 | )
10 | })
11 | })
12 |
--------------------------------------------------------------------------------
/modules/__tests__/cssifyObject-test.ts:
--------------------------------------------------------------------------------
1 | import cssifyObject from '../cssifyObject'
2 |
3 | describe('Cssifying objects', () => {
4 | it('should generate a valid CSS string', () => {
5 | expect(cssifyObject({ color: 'red' })).toEqual('color:red')
6 | })
7 |
8 | it('should convert properties to dash case', () => {
9 | expect(cssifyObject({ fontSize: '12px' })).toEqual('font-size:12px')
10 | })
11 |
12 | it('should separate declarations with semicolons', () => {
13 | expect(
14 | cssifyObject({
15 | fontSize: '12px',
16 | color: 'red',
17 | })
18 | ).toEqual('font-size:12px;color:red')
19 | })
20 |
21 | it('should convert vendor prefixes', () => {
22 | expect(
23 | cssifyObject({
24 | WebkitJustifyContent: 'center',
25 | msFlexAlign: 'center',
26 | })
27 | ).toEqual('-webkit-justify-content:center;-ms-flex-align:center')
28 | })
29 | })
30 |
--------------------------------------------------------------------------------
/modules/__tests__/isPrefixedProperty-test.ts:
--------------------------------------------------------------------------------
1 | import isPrefixedProperty from '../isPrefixedProperty'
2 |
3 | describe('Checking for prefixed properties', () => {
4 | it('should return true', () => {
5 | expect(isPrefixedProperty('WebkitTransition')).toEqual(true)
6 | expect(isPrefixedProperty('msTransitionDelay')).toEqual(true)
7 | })
8 |
9 | it('should return false', () => {
10 | expect(isPrefixedProperty('transition')).toEqual(false)
11 | expect(isPrefixedProperty('transitionDelay')).toEqual(false)
12 | })
13 | })
14 |
--------------------------------------------------------------------------------
/modules/__tests__/isPrefixedValue-test.ts:
--------------------------------------------------------------------------------
1 | import isPrefixedValue from '../isPrefixedValue'
2 |
3 | describe('Checking for prefixed values', () => {
4 | it('should return true', () => {
5 | expect(isPrefixedValue('-webkit-calc(100% - 20px)')).toEqual(true)
6 | expect(isPrefixedValue('-ms-transition')).toEqual(true)
7 | })
8 |
9 | it('should return false', () => {
10 | expect(isPrefixedValue('200px')).toEqual(false)
11 | expect(isPrefixedValue('calc(100% - 20px)')).toEqual(false)
12 | })
13 | })
14 |
--------------------------------------------------------------------------------
/modules/__tests__/isUnitlessProperty-test.ts:
--------------------------------------------------------------------------------
1 | import isUnitlessProperty from '../isUnitlessProperty'
2 |
3 | describe('Checking for unitless CSS properties', () => {
4 | it('should return true for unitless properties', () => {
5 | expect(isUnitlessProperty('fontWeight')).toEqual(true)
6 | expect(isUnitlessProperty('flex')).toEqual(true)
7 | expect(isUnitlessProperty('gridColumn')).toEqual(true)
8 | })
9 |
10 | it('should return true for hypenated unitless properties', () => {
11 | expect(isUnitlessProperty('font-weight')).toEqual(true)
12 | expect(isUnitlessProperty('grid-column')).toEqual(true)
13 | })
14 |
15 | it('should return true for prefixed unitless properties', () => {
16 | expect(isUnitlessProperty('WebkitFlex')).toEqual(true)
17 | expect(isUnitlessProperty('msFlex')).toEqual(true)
18 | expect(isUnitlessProperty('WebkitColumnCount')).toEqual(true)
19 | expect(isUnitlessProperty('msColumnCount')).toEqual(true)
20 | })
21 |
22 | it('should return true for hypenated prefixed unitless properties', () => {
23 | expect(isUnitlessProperty('-webkit-flex')).toEqual(true)
24 | expect(isUnitlessProperty('-ms-flex')).toEqual(true)
25 | expect(isUnitlessProperty('-webkit-column-count')).toEqual(true)
26 | expect(isUnitlessProperty('-ms-column-count')).toEqual(true)
27 | })
28 |
29 | it('should equal false for other properties', () => {
30 | expect(isUnitlessProperty('fontSize')).toEqual(false)
31 | expect(isUnitlessProperty('font-size')).toEqual(false)
32 | expect(isUnitlessProperty('-webkit-border-radius')).toEqual(false)
33 | expect(isUnitlessProperty('-ms-border-radius')).toEqual(false)
34 | expect(isUnitlessProperty('WebkitBorderRadius')).toEqual(false)
35 | expect(isUnitlessProperty('msBorderRadius')).toEqual(false)
36 | })
37 | })
38 |
--------------------------------------------------------------------------------
/modules/__tests__/normalizeProperty-test.ts:
--------------------------------------------------------------------------------
1 | import normalizeProperty from '../normalizeProperty'
2 |
3 | describe('Normalizing properties', () => {
4 | it('should camel case hypenated properties', () => {
5 | expect(normalizeProperty('transition-delay')).toEqual('transitionDelay')
6 | })
7 |
8 | it('should unprefix properties', () => {
9 | expect(normalizeProperty('WebkitTransitionDelay')).toEqual(
10 | 'transitionDelay'
11 | )
12 | })
13 |
14 | it('should unprefix and camel case properties', () => {
15 | expect(normalizeProperty('-webkit-transition-delay')).toEqual(
16 | 'transitionDelay'
17 | )
18 | })
19 | })
20 |
--------------------------------------------------------------------------------
/modules/__tests__/resolveArrayValue-test.ts:
--------------------------------------------------------------------------------
1 | import resolveArrayValue from '../resolveArrayValue'
2 |
3 | describe('Resolving array values', () => {
4 | it('should return a concatenated css value', () => {
5 | expect(resolveArrayValue('width', ['300px', '100px'])).toEqual(
6 | '300px;width:100px'
7 | )
8 | })
9 |
10 | it('should hyphenate property names', () => {
11 | expect(resolveArrayValue('WebkitFlex', [1, 2, 3])).toEqual(
12 | '1;-webkit-flex:2;-webkit-flex:3'
13 | )
14 | })
15 | })
16 |
--------------------------------------------------------------------------------
/modules/__tests__/unprefixProperty-test.ts:
--------------------------------------------------------------------------------
1 | import unprefixProperty from '../unprefixProperty'
2 |
3 | describe('Unprefixing properties', () => {
4 | it('should unprefix the property', () => {
5 | expect(unprefixProperty('msFlex')).toEqual('flex')
6 | expect(unprefixProperty('WebkitFlex')).toEqual('flex')
7 | })
8 |
9 | it('should keep an unprefixed property', () => {
10 | expect(unprefixProperty('flex')).toEqual('flex')
11 | expect(unprefixProperty('padding')).toEqual('padding')
12 | })
13 | })
14 |
--------------------------------------------------------------------------------
/modules/__tests__/unprefixValue-test.ts:
--------------------------------------------------------------------------------
1 | import unprefixValue from '../unprefixValue'
2 |
3 | describe('Unprefixing values', () => {
4 | it('should unprefix the value', () => {
5 | expect(unprefixValue('-webkit-calc(100% - 20px)')).toEqual(
6 | 'calc(100% - 20px)'
7 | )
8 | expect(unprefixValue('-ms-transition')).toEqual('transition')
9 | })
10 |
11 | it('should keep an unprefixed value', () => {
12 | expect(unprefixValue('300px')).toEqual('300px')
13 | expect(unprefixValue(300)).toEqual(300)
14 | expect(unprefixValue('calc(100% - 20px)')).toEqual('calc(100% - 20px)')
15 | })
16 | })
17 |
--------------------------------------------------------------------------------
/modules/assignStyle.ts:
--------------------------------------------------------------------------------
1 | function filterUniqueArray(arr: any[]) {
2 | return arr.filter((val, index) => arr.lastIndexOf(val) === index)
3 | }
4 |
5 | type StyleObject = {
6 | [key: string]:
7 | | string
8 | | number
9 | | StyleObject
10 | | (string | number | StyleObject)[]
11 | }
12 |
13 | export default function assignStyle(
14 | base: StyleObject,
15 | ...extendingStyles: StyleObject[]
16 | ) {
17 | for (let i = 0, len = extendingStyles.length; i < len; ++i) {
18 | const style = extendingStyles[i]
19 |
20 | for (const property in style) {
21 | const value = style[property]
22 | const baseValue = base[property]
23 |
24 | if (baseValue && value) {
25 | if (Array.isArray(baseValue)) {
26 | base[property] = filterUniqueArray(baseValue.concat(value))
27 | continue
28 | }
29 |
30 | if (Array.isArray(value)) {
31 | base[property] = filterUniqueArray([baseValue, ...value])
32 | continue
33 | }
34 |
35 | if (typeof value === 'object') {
36 | base[property] = assignStyle({}, baseValue as StyleObject, value)
37 | continue
38 | }
39 | }
40 |
41 | base[property] = value
42 | }
43 | }
44 |
45 | return base
46 | }
47 |
--------------------------------------------------------------------------------
/modules/camelCaseProperty.ts:
--------------------------------------------------------------------------------
1 | type CacheObject = {
2 | [key: string]: string
3 | }
4 |
5 | const CSS_VARIABLE = /^--/;
6 | const DASH = /-([a-z])/g
7 | const MS = /^Ms/g
8 | const cache: CacheObject = {}
9 |
10 | function toUpper(match: string) {
11 | return match[1].toUpperCase()
12 | }
13 |
14 | export default function camelCaseProperty(property: string) {
15 | if (cache.hasOwnProperty(property)) {
16 | return cache[property]
17 | }
18 |
19 | const camelProp = CSS_VARIABLE.test(property) ? property : property.replace(DASH, toUpper).replace(MS, 'ms')
20 | cache[property] = camelProp
21 |
22 | return camelProp
23 | }
24 |
--------------------------------------------------------------------------------
/modules/cssifyDeclaration.ts:
--------------------------------------------------------------------------------
1 | import hyphenateProperty from './hyphenateProperty'
2 |
3 | export default function cssifyDeclaration(
4 | property: string,
5 | value: string | number
6 | ) {
7 | return hyphenateProperty(property) + ':' + value
8 | }
9 |
--------------------------------------------------------------------------------
/modules/cssifyObject.ts:
--------------------------------------------------------------------------------
1 | import cssifyDeclaration from './cssifyDeclaration'
2 |
3 | export type StyleObject = {
4 | [key: string]:
5 | | string
6 | | number
7 | | StyleObject
8 | | (string | number | StyleObject)[]
9 | }
10 |
11 | export default function cssifyObject(style: StyleObject) {
12 | let css = ''
13 |
14 | for (const property in style) {
15 | const value = style[property]
16 | if (typeof value !== 'string' && typeof value !== 'number') {
17 | continue
18 | }
19 |
20 | // prevents the semicolon after
21 | // the last rule declaration
22 | if (css) {
23 | css += ';'
24 | }
25 |
26 | css += cssifyDeclaration(property, value)
27 | }
28 |
29 | return css
30 | }
31 |
--------------------------------------------------------------------------------
/modules/hyphenate-style-name.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'hyphenate-style-name' {
2 | export default function hyphenateStyleName(property: string | number): string
3 | }
4 |
--------------------------------------------------------------------------------
/modules/hyphenateProperty.ts:
--------------------------------------------------------------------------------
1 | import hyphenateStyleName from 'hyphenate-style-name'
2 |
3 | export default function hyphenateProperty(property: string | number) {
4 | return hyphenateStyleName(property)
5 | }
6 |
--------------------------------------------------------------------------------
/modules/index.ts:
--------------------------------------------------------------------------------
1 | import assignStyle from './assignStyle'
2 | import camelCaseProperty from './camelCaseProperty'
3 | import cssifyDeclaration from './cssifyDeclaration'
4 | import cssifyObject from './cssifyObject'
5 | import hyphenateProperty from './hyphenateProperty'
6 | import isPrefixedProperty from './isPrefixedProperty'
7 | import isPrefixedValue from './isPrefixedValue'
8 | import isUnitlessProperty from './isUnitlessProperty'
9 | import normalizeProperty from './normalizeProperty'
10 | import resolveArrayValue from './resolveArrayValue'
11 | import unprefixProperty from './unprefixProperty'
12 | import unprefixValue from './unprefixValue'
13 |
14 | export {
15 | assignStyle,
16 | camelCaseProperty,
17 | cssifyDeclaration,
18 | cssifyObject,
19 | hyphenateProperty,
20 | isPrefixedProperty,
21 | isPrefixedValue,
22 | isUnitlessProperty,
23 | normalizeProperty,
24 | resolveArrayValue,
25 | unprefixProperty,
26 | unprefixValue,
27 | }
28 |
--------------------------------------------------------------------------------
/modules/isPrefixedProperty.ts:
--------------------------------------------------------------------------------
1 | const RE = /^(Webkit|Moz|O|ms)/
2 |
3 | export default function isPrefixedProperty(property: string) {
4 | return RE.test(property)
5 | }
6 |
--------------------------------------------------------------------------------
/modules/isPrefixedValue.ts:
--------------------------------------------------------------------------------
1 | const RE = /-webkit-|-moz-|-ms-/
2 |
3 | export default function isPrefixedValue(value: string) {
4 | return typeof value === 'string' && RE.test(value)
5 | }
6 |
--------------------------------------------------------------------------------
/modules/isUnitlessProperty.ts:
--------------------------------------------------------------------------------
1 | import hyphenateProperty from './hyphenateProperty'
2 |
3 | const unitlessProperties: { [key: string]: boolean } = {
4 | borderImageOutset: true,
5 | borderImageSlice: true,
6 | borderImageWidth: true,
7 | fontWeight: true,
8 | lineHeight: true,
9 | opacity: true,
10 | orphans: true,
11 | tabSize: true,
12 | widows: true,
13 | zIndex: true,
14 | zoom: true,
15 | // SVG-related properties
16 | fillOpacity: true,
17 | floodOpacity: true,
18 | stopOpacity: true,
19 | strokeDasharray: true,
20 | strokeDashoffset: true,
21 | strokeMiterlimit: true,
22 | strokeOpacity: true,
23 | strokeWidth: true,
24 | }
25 |
26 | const prefixedUnitlessProperties = [
27 | 'animationIterationCount',
28 | 'boxFlex',
29 | 'boxFlexGroup',
30 | 'boxOrdinalGroup',
31 | 'columnCount',
32 | 'flex',
33 | 'flexGrow',
34 | 'flexPositive',
35 | 'flexShrink',
36 | 'flexNegative',
37 | 'flexOrder',
38 | 'gridColumn',
39 | 'gridColumnEnd',
40 | 'gridColumnStart',
41 | 'gridRow',
42 | 'gridRowEnd',
43 | 'gridRowStart',
44 | 'lineClamp',
45 | 'order',
46 | ]
47 |
48 | const prefixes = ['Webkit', 'ms', 'Moz', 'O']
49 |
50 | function getPrefixedProperty(prefix: string, property: string) {
51 | return prefix + property.charAt(0).toUpperCase() + property.slice(1)
52 | }
53 |
54 | // add all prefixed properties to the unitless properties
55 | for (let i = 0, len = prefixedUnitlessProperties.length; i < len; ++i) {
56 | const property = prefixedUnitlessProperties[i]
57 | unitlessProperties[property] = true
58 |
59 | for (let j = 0, jLen = prefixes.length; j < jLen; ++j) {
60 | unitlessProperties[getPrefixedProperty(prefixes[j], property)] = true
61 | }
62 | }
63 |
64 | // add all hypenated properties as well
65 | for (const property in unitlessProperties) {
66 | unitlessProperties[hyphenateProperty(property)] = true
67 | }
68 |
69 | export default function isUnitlessProperty(property: string) {
70 | return unitlessProperties.hasOwnProperty(property)
71 | }
72 |
--------------------------------------------------------------------------------
/modules/normalizeProperty.ts:
--------------------------------------------------------------------------------
1 | import camelCaseProperty from './camelCaseProperty'
2 | import unprefixProperty from './unprefixProperty'
3 |
4 | export default function normalizeProperty(property: string) {
5 | return unprefixProperty(camelCaseProperty(property))
6 | }
7 |
--------------------------------------------------------------------------------
/modules/resolveArrayValue.ts:
--------------------------------------------------------------------------------
1 | import hyphenateProperty from './hyphenateProperty'
2 |
3 | export default function resolveArrayValue(property: string, value: string[]) {
4 | return value.join(';' + hyphenateProperty(property) + ':')
5 | }
6 |
--------------------------------------------------------------------------------
/modules/unprefixProperty.ts:
--------------------------------------------------------------------------------
1 | const RE = /^(ms|Webkit|Moz|O)/
2 |
3 | export default function unprefixProperty(property: string) {
4 | const propertyWithoutPrefix = property.replace(RE, '')
5 | return (
6 | propertyWithoutPrefix.charAt(0).toLowerCase() +
7 | propertyWithoutPrefix.slice(1)
8 | )
9 | }
10 |
--------------------------------------------------------------------------------
/modules/unprefixValue.ts:
--------------------------------------------------------------------------------
1 | const RE = /(-ms-|-webkit-|-moz-|-o-)/g
2 |
3 | export default function unprefixValue(value: string | string[]) {
4 | if (typeof value === 'string') {
5 | return value.replace(RE, '')
6 | }
7 |
8 | return value
9 | }
10 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "css-in-js-utils",
3 | "version": "3.1.0",
4 | "description": "Useful utility functions for CSS in JS solutions",
5 | "main": "lib/index.js",
6 | "module": "es/index.js",
7 | "types": "es/index.d.ts",
8 | "jsnext:main": "es/index.js",
9 | "sideEffects": false,
10 | "files": [
11 | "lib/**",
12 | "es/**"
13 | ],
14 | "keywords": [
15 | "css",
16 | "cssinjs",
17 | "utils",
18 | "small"
19 | ],
20 | "repository": "https://github.com/robinweser/css-in-js-utils.git",
21 | "author": "robinweser ",
22 | "license": "MIT",
23 | "scripts": {
24 | "build": "yarn run clean && yarn build:es && yarn build:lib",
25 | "build:lib": "cross-env BABEL_ENV=commonjs babel modules --extensions '.ts' --ignore 'modules/__tests__','**/*.d.ts' --out-dir lib",
26 | "build:es": "tsc && babel es --out-dir es",
27 | "clean": "rimraf es lib coverage",
28 | "check": "yarn lint && yarn test:coverage",
29 | "format": "prettier --write \"modules/**/*.{js,ts}\"",
30 | "lint": "eslint 'modules/**/*.{js,ts}'",
31 | "release": "git pull --rebase && yarn run check && yarn build && npm publish",
32 | "test": "cross-env BABEL_ENV=commonjs jest",
33 | "test:coverage": "cross-env BABEL_ENV=commonjs jest --coverage",
34 | "watch": "yarn test -- --watch"
35 | },
36 | "jest": {
37 | "moduleDirectories": [
38 | "node_modules",
39 | "modules"
40 | ]
41 | },
42 | "dependencies": {
43 | "hyphenate-style-name": "^1.0.3"
44 | },
45 | "devDependencies": {
46 | "@babel/cli": "^7.8.4",
47 | "@babel/core": "^7.9.0",
48 | "@babel/preset-env": "^7.9.5",
49 | "@babel/preset-typescript": "^7.9.0",
50 | "@types/jest": "^25.2.1",
51 | "@typescript-eslint/eslint-plugin": "^2.26.0",
52 | "@typescript-eslint/parser": "^2.26.0",
53 | "babel-jest": "^25.2.6",
54 | "babel-plugin-transform-es2015-modules-commonjs": "^6.26.2",
55 | "cross-env": "^7.0.2",
56 | "eslint": "^6.8.0",
57 | "eslint-config-airbnb-base": "^14.1.0",
58 | "eslint-plugin-import": "^2.20.2",
59 | "jest": "^25.2.6",
60 | "prettier": "^1.7.4",
61 | "rimraf": "^2.6.1",
62 | "typescript": "^3.8.3"
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowJs": true,
4 | "declaration": true,
5 | "module": "ESNext",
6 | "moduleResolution": "node",
7 | "outDir": "es",
8 | "skipLibCheck": true,
9 | "strict": true,
10 | "target": "ESNext",
11 | "types": ["jest"]
12 | },
13 | "exclude": ["modules/__tests__", "node_modules"],
14 | "include": ["modules"]
15 | }
16 |
--------------------------------------------------------------------------------