├── .eslintrc
├── .gitignore
├── .npmignore
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── Makefile
├── README.md
├── babel.config.js
├── dist
├── index.js
└── utils
│ ├── CSSProperty.js
│ ├── CSSPropertyOperations.js
│ ├── dangerousStyleValue.js
│ ├── fbjs.js
│ └── index.js
├── lib
├── __tests__
│ ├── fixtures.js
│ ├── lib.test.js
│ └── utils.test.js
├── index.js
└── utils
│ ├── CSSProperty.js
│ ├── CSSPropertyOperations.js
│ ├── dangerousStyleValue.js
│ ├── fbjs.js
│ └── index.js
├── package-lock.json
├── package.json
└── stilr.d.ts
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "es6": true,
4 | "browser": true,
5 | "node": true,
6 | "mocha": true
7 | },
8 | "parserOptions": {
9 | "ecmaVersion": 6,
10 | "sourceType": "module"
11 | },
12 | "extends": "eslint:recommended",
13 | "rules": {
14 | "quotes": [2, "single"],
15 | "strict": [2, "never"],
16 | "curly": [0],
17 | "no-underscore-dangle": [0],
18 | "prefer-const": [2]
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | npm-debug.log
3 | .DS_Store
4 | .tern*
5 | dist/__tests__
6 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | src/__tests__
2 | lib/__tests__
3 | dist/__tests__
4 | example
5 | .tern*
6 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - 'node'
4 | - '6'
5 | - '5'
6 | - '4'
7 | script: make build
8 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | > **Tags:**
4 | > - [New Feature]
5 | > - [Bug Fix]
6 | > - [Breaking Change]
7 | > - [Documentation]
8 | > - [Internal]
9 | > - [Polish]
10 |
11 | _Note: Gaps between patch versions are faulty/broken releases._
12 |
13 | ## 1.2.1
14 | * Feature
15 | * Exposes the Map class that's used internally to create
16 | stylesheet.
17 | Usage: `import Stylesheet from 'stilr'` `Stylesheet.Map`
18 | * Bug
19 | * Fixed stylesheet instanceof check
20 |
21 | ## 1.1.0
22 | * __Internal__
23 | * Added babel-runtime as a dependency.
24 |
25 | ## 1.0.0
26 | __Yeah, 1.0.0!__
27 | The Api have been solidified, It's used in production, there's good test
28 | coverage, let's :shipit: !
29 |
30 | * __Feature__
31 | * It's now possible to use pseudo selectors inside media queries.
32 |
33 | ## 0.3.0
34 | * __Internal__
35 | * Media queries are now inserted after normal CSS
36 | * Null and empty classes are now removed from rendered CSS
37 |
38 | ## 0.2.2
39 | * __Bug Fix__
40 | * Media queries are now inserted after parent entry. Thanks @MicheleBertoli
41 | !
42 | * New entries in existing media queries don't overwrite existing entries
43 |
44 | ## 0.2.0
45 |
46 | * **Internal**
47 | * Implemented extentedToString, now we have even shorter class names!
48 |
49 | ## 0.1.0
50 |
51 | * **Internal**
52 | * createHash was split into 3 helpers. `createHash`, `stringifyObject`, `createClassName`
53 | * Optimized stringifyObject.
54 | * renamed normalizeObj to sortObject.
55 |
56 |
57 | ## 0.0.1
58 |
59 | * Initial Release
60 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015-present Kodyl ApS
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 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | BIN = ./node_modules/.bin
2 | PATH := $(BIN):$(PATH)
3 | LIB = $(shell find lib -name "*.js")
4 | DIST = $(patsubst lib/%.js,dist/%.js,$(LIB))
5 |
6 | MOCHA_ARGS = --require mocha-clean
7 |
8 | MOCHA_DEV = $(MOCHA_ARGS) \
9 | --require @babel/polyfill \
10 | --compilers js:@babel/register \
11 | ./lib/__tests__/*.test.js
12 |
13 | MOCHA_DIST = $(MOCHA_ARGS) \
14 | ./dist/__tests__/*.test.js
15 |
16 | define release
17 | VERSION=`node -pe "require('./package.json').version"` && \
18 | NEXT_VERSION=`node -pe "require('semver').inc(\"$$VERSION\", '$(1)')"` && \
19 | git flow release start $$NEXT_VERSION && \
20 | make build && \
21 | npm --no-git-tag-version version $(1) -m 'release %s' && \
22 | git add . && \
23 | git commit -m 'make build and release' && \
24 | git flow release finish -m $$NEXT_VERSION $$NEXT_VERSION
25 | endef
26 |
27 | dist: $(DIST)
28 | dist/%.js: lib/%.js
29 | @echo "Building $<"
30 | @mkdir -p $(@D)
31 | @$(BIN)/babel $< -o $@
32 |
33 | clean:
34 | @echo "\nRemove existing build files..."
35 | @rm -rf ./dist
36 |
37 | link:
38 | @npm link
39 |
40 | lint:
41 | @echo "\nLinting source files..."
42 | @$(BIN)/eslint lib/
43 |
44 | test:
45 | @echo "\nTesting source files, hang on..."
46 | @$(BIN)/mocha $(MOCHA_DEV)
47 |
48 | test-watch:
49 | @echo "\nStarting test watcher..."
50 | @$(BIN)/mocha $(MOCHA_DEV) --watch
51 |
52 | test-dist:
53 | @echo "\nTesting build files, almost there..!"
54 | @$(BIN)/mocha $(MOCHA_DIST)
55 |
56 | build: lint test clean dist test-dist
57 |
58 | build-local: build link
59 |
60 | release-patch:
61 | $(call release,patch)
62 |
63 | release-minor:
64 | $(call release,minor)
65 |
66 | release-major:
67 | $(call release,major)
68 |
69 | .PHONY: \
70 | build \
71 | build-local \
72 | install \
73 | link \
74 | release-major \
75 | release-minor \
76 | release-patch \
77 | test \
78 | test-dist \
79 | test-watch \
80 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Stilr [](https://travis-ci.org/kodyl/stilr) [](http://badge.fury.io/js/stilr)
2 |
3 | Encapsulated styling for your javascript components with all the power of
4 | javascript and CSS combined.
5 |
6 | - Unique class names (Content Hash Based)
7 | - Useable on the server
8 | - Allows nested pseudo selectors
9 | - Allows nested media queries
10 | - No namespacing / Class name collisions.
11 | - Plays nicely with React Hot Loader and autoprefixer.
12 |
13 | ...oh, and did I mention you get duplicate style elimination for free?
14 |
15 | __Note__: This library fits really nice with [React](https://facebook.github.io/react/) but should work with other libraries like [Angular 2](https://angular.io/) or [Deku](https://github.com/dekujs/deku)
16 |
17 | ## API
18 |
19 | #### `object StyleSheet.create(object spec)`
20 | Stilr extracts the styles from the style object and returns an object with the
21 | same keys mapped to class names.
22 |
23 | __Example__
24 | ```javascript
25 | import StyleSheet from 'stilr';
26 |
27 | const palm = '@media screen and (max-width:600px)';
28 |
29 | const styles = StyleSheet.create({
30 | container: {
31 | color: '#fff',
32 | ':hover': { // Pseudo Selectors are allowed
33 | color: '#000'
34 | },
35 | [palm]: { // Media Queries are allowed
36 | fontSize: 16,
37 | ':hover': {
38 | color: 'blue' // Pseudo selectors inside media queries.
39 | }
40 | }
41 | }
42 | });
43 |
44 | console.log(styles.container); // => '_xsrhhm' -- (The class name for this style.)
45 | ```
46 |
47 | #### `string StyleSheet.render()`
48 | Stilr outputs the contents of its internal stylesheet as a string of css
49 |
50 | __Example__
51 | ```javascript
52 | import StyleSheet from 'stilr';
53 |
54 | StyleSheet.create({
55 | container: {
56 | color: '#fff'
57 | }
58 | });
59 |
60 | const CSS = StyleSheet.render();
61 |
62 | console.log(CSS); // => '._yiw79c{color:#fff;}'
63 | ```
64 |
65 | #### `bool StyleSheet.clear()`
66 | Clear Stilr internal stylesheet
67 |
68 | __Example__
69 | ```javascript
70 | import StyleSheet from 'stilr';
71 |
72 | const styles = StyleSheet.create({
73 | container: {
74 | color: '#fff'
75 | }
76 | });
77 |
78 | StyleSheet.clear();
79 |
80 | const CSS = StyleSheet.render();
81 |
82 | console.log(CSS); // => ''
83 | ```
84 |
85 |
86 | ## Examples
87 |
88 | #### Basic Button Component Example.
89 | Let's start of by creating our styles. If you have ever used React Native, this
90 | will be familiar to you:
91 |
92 | ```javascript
93 | import StyleSheet from 'stilr';
94 | import { palm } from './breakpoints';
95 | import { color, font } from './theme';
96 |
97 | const styles = StyleSheet.create({
98 | base: {
99 | transition: 'background-color .25s',
100 | borderRadius: 2,
101 | textAlign: 'center',
102 | fontSize: 20,
103 | padding: 6,
104 | color: '#fff',
105 | border: `${ color.border } 1px solid`,
106 | [palm]: {
107 | fontSize: 18
108 | }
109 | },
110 | primary: {
111 | backgroundColor: color.primary,
112 | ':hover': {
113 | color: 'tomato'
114 | }
115 | },
116 | secondary: {
117 | backgroundColor: 'tomato',
118 | color: '#eee'
119 | }
120 | });
121 | ```
122 |
123 | Stilr will now generate a set of class names based on the content of your styles
124 | and return an object with the same keys mapped to those classes.
125 |
126 | Note that you're able to use pseudo selectors and media queries.
127 | Pseudo selectors are written like you normally would in CSS, e.g.: `:hover`, `:active`,
128 | `:before` etc.
129 | Media queries are the same, e.g. `palm` in the example is just a string: `@media screen and (max-width:600px)`. Any valid media query is allowed.
130 |
131 | Since we just have a bunch of class names now, we can use these in our React
132 | Component.
133 |
134 | ```javascript
135 | import React, { PropTypes } from 'react';
136 |
137 | class Button extends React.Component {
138 | static propTypes = {
139 | type: PropTypes.oneOf(['primary', 'secondary'])
140 | }
141 |
142 | render() {
143 | const { type, children } = this.props;
144 | const buttonStyles = [
145 | styles.base,
146 | styles[ type ]
147 | ].join(' ');
148 |
149 | return (
150 |
153 | );
154 | }
155 | ```
156 |
157 | Next up, let's render our css and mount our app:
158 |
159 | ```javascript
160 | import React from 'react';
161 | import Button from './button';
162 | import StyleSheet from 'stilr';
163 |
164 | React.render(
165 | ,
166 | document.getElementById('root')
167 | );
168 |
169 | document.getElementById('stylesheet').textContent = StyleSheet.render();
170 | ```
171 |
172 | This step could also have been done on the server. Since StyleSheet.render just
173 | returns a string of css. In our case, it would look something like this in a
174 | prettified version:
175 | ```css
176 | @media screen and (max-width:600px) {
177 | ._82uwp6 {
178 | font-size: 18px;
179 | }
180 | }
181 |
182 | ._82uwp6 {
183 | transition: background-color .25s;
184 | border-radius: 2px;
185 | text-align: center;
186 | font-size: 20px;
187 | padding: 6px;
188 | color: #fff;
189 | border: #fff 1px solid;
190 | }
191 |
192 | ._11jt6vs:hover {
193 | color: tomato;
194 | }
195 |
196 | ._11jt6vs {
197 | background-color: red;
198 | }
199 |
200 | ._1f4wq27 {
201 | background-color: tomato;
202 | color: #eee;
203 | }
204 | ```
205 |
206 | In case you were wondering: Yes, this would would be an ideal place to add something like autoprefixer, minification etc.
207 |
208 |
209 | ## Duplicate Style Elimation
210 | ```javascript
211 | import StyleSheet from 'stilr';
212 |
213 | const styles = StyleSheet.create({
214 | same: {
215 | fontSize: 18,
216 | color: '#000'
217 | },
218 | sameSame: {
219 | fontSize: 18,
220 | color: '#000'
221 | }
222 | });
223 |
224 | console.log( styles.same ); => '_1v3qejj'
225 | console.log( styles.sameSame ); => '_1v3qejj'
226 | ```
227 |
228 | ...magic.
229 |
230 | Under the hood, stilr creates class names based on a content hash of your style object
231 | which means that when the content is the same, the same hash will always be
232 | returned.
233 |
234 |
235 | ## Extracting your styles.
236 |
237 | #### Server
238 | If you do serverside rendering, you should be able to extract your styles right after you load your app.
239 |
240 | ```javascript
241 | import React from 'react';
242 | import StyleSheet from 'stilr';
243 | import App from '../your-app.js';
244 |
245 | const css = StyleSheet.render();
246 | const html = React.renderToStaticMarkup();
247 |
248 | // Extract css to a file or insert it in a file at the top of your html document
249 | ```
250 | Apply autoprefixer here, or other preprocess goodness here.
251 | If you're really fancy, you only do the required autoprefixes based on the user agent.
252 |
253 | ### Development
254 | When working with Stilr in development, the preferred way to extract styles would be the following way, just before you initialize your app.
255 |
256 | ```javascript
257 | import App from '../app';
258 | import React from 'react';
259 | import StyleSheet from 'stilr';
260 |
261 | let stylesheet = document.createElement('style');
262 | stylesheet.textContent = StyleSheet.render();
263 | document.head.appendChild(stylesheet);
264 |
265 | React.render(, document.getElementById('root'));
266 | ```
267 |
268 | ### React Hot Loader + Autoprefixer
269 | If you're using [React Hot Loader](https://github.com/gaearon/react-hot-loader). Use the following approach in development to get hot loading styles and autoprefixer awesomeness.
270 |
271 | ```javascript
272 | import React from 'react';
273 | import StyleSheet from 'stilr';
274 | import autoprefixer from 'autoprefixer';
275 | import postcss from 'postcss';
276 |
277 |
278 | // Make sure you have a style element with the ID: 'stylesheet' in your html.
279 | const stylesheet = document.getElementById('stylesheet');
280 |
281 | class App extends React.Component {
282 | render() {
283 | if (process.env !== 'production') {
284 | const prefixedCSS = postcss(autoprefixer()).process( StyleSheet.render() ).css;
285 | stylesheet.textContent = prefixedCSS;
286 | }
287 |
288 | return (
289 | ...snip...
290 | );
291 | }
292 | }
293 | ```
294 | If you're using Webpack, you have to add `node: { fs: 'empty' }` to your config file. Otherwise autoprefixer will throw an error when trying to use it on the client.
295 |
296 | #### Production
297 |
298 | ##### Makefile
299 | Add this as a build step in your makefile.
300 | ```Makefile
301 | extract-styles:
302 | @node -p "require('babel/register')({ignore: false}); var s = require('stilr'); require('./your-app.js'); s.render()" > ./bundle.css
303 | ```
304 |
305 | The following snippet can also be executed anywhere to extract the styles.
306 | Remember to replace `./your-app.js` with the entry file of your app.
307 | `node -p "require('babel/register')({ignore: false}); var s = require('stilr'); require('./your-app.js')`
308 |
309 |
310 | ## TODO:
311 | - [ ] Remove React as a dependency
312 | - [ ] More examples
313 |
314 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = function(api) {
2 | api.cache(true);
3 |
4 | return {
5 | presets: [['@babel/preset-env']],
6 | plugins: [['@babel/plugin-transform-runtime']]
7 | };
8 | };
9 |
--------------------------------------------------------------------------------
/dist/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports["default"] = void 0;
7 |
8 | var _utils = require("./utils");
9 |
10 | var globalStylesheet = new Map();
11 | var _default = {
12 | create: function create(styles) {
13 | var stylesheet = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : globalStylesheet;
14 | if (!(stylesheet instanceof Map)) throw new Error("".concat(stylesheet, " should be a Map"));
15 | return Object.keys(styles).reduce(function (acc, key) {
16 | var _seperateStyles = (0, _utils.seperateStyles)(styles[key]),
17 | style = _seperateStyles.style,
18 | pseudos = _seperateStyles.pseudos,
19 | mediaQueries = _seperateStyles.mediaQueries;
20 |
21 | var className = (0, _utils.createClassName)((0, _utils.sortObject)(style));
22 |
23 | if (className === undefined) {
24 | acc[key] = '';
25 | return acc;
26 | }
27 |
28 | if (!stylesheet.has(className)) stylesheet.set(className, style);
29 |
30 | if (pseudos.length) {
31 | pseudos.map(function (selector) {
32 | delete style[selector];
33 | var pseudoClassName = "".concat(className).concat(selector);
34 | if (stylesheet.has(pseudoClassName)) return false;
35 | stylesheet.set(pseudoClassName, styles[key][selector]);
36 | });
37 | }
38 |
39 | if (mediaQueries.length) {
40 | mediaQueries.map(function (selector) {
41 | var mqSelector = selector;
42 | var mqStyles = styles[key][selector];
43 | var mqPseudos = [];
44 | var mqStylesheet;
45 |
46 | if (Array.isArray(selector)) {
47 | mqSelector = selector[0];
48 | mqStyles = selector[1];
49 | mqPseudos = selector.slice(2);
50 | }
51 |
52 | delete style[mqSelector];
53 |
54 | if (stylesheet.has(mqSelector)) {
55 | mqStylesheet = stylesheet.get(mqSelector);
56 | if (mqStylesheet.has(className)) return false;
57 | }
58 |
59 | mqStylesheet = mqStylesheet || stylesheet.set(mqSelector, new Map()).get(mqSelector);
60 | mqStylesheet.set(className, mqStyles);
61 |
62 | if (mqPseudos.length) {
63 | mqPseudos.map(function (pseudo) {
64 | delete mqStyles[pseudo];
65 | var pseudoClassName = "".concat(className).concat(pseudo);
66 | if (mqStylesheet.has(pseudoClassName)) return false;
67 | mqStylesheet.set(pseudoClassName, styles[key][mqSelector][pseudo]);
68 | });
69 | }
70 | });
71 | }
72 |
73 | acc[key] = className;
74 | return acc;
75 | }, {});
76 | },
77 | render: function render() {
78 | var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {
79 | pretty: false
80 | };
81 | var stylesheet = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : globalStylesheet;
82 | var stylesheetEntries = stylesheet.entries();
83 | var css = '';
84 | var mediaQueries = '';
85 | var _iteratorNormalCompletion = true;
86 | var _didIteratorError = false;
87 | var _iteratorError = undefined;
88 |
89 | try {
90 | for (var _iterator = stylesheetEntries[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
91 | var entry = _step.value;
92 | var className = entry[0];
93 | var styles = entry[1];
94 | var isMap = styles instanceof Map;
95 | if (!isMap && (0, _utils.isEmpty)(styles)) continue;
96 |
97 | if (isMap) {
98 | var mediaQueryCSS = this.render(options, stylesheet.get(className));
99 | mediaQueries += options.pretty ? "".concat(className, " {\n").concat(mediaQueryCSS, "}\n") : "".concat(className, "{").concat(mediaQueryCSS, "}");
100 | continue;
101 | }
102 |
103 | var markup = (0, _utils.createMarkup)(styles);
104 | css += options.pretty ? ".".concat(className, " {\n").concat(markup.split(';').join(';\n'), "}\n") : ".".concat(className, "{").concat(markup, "}");
105 | }
106 | } catch (err) {
107 | _didIteratorError = true;
108 | _iteratorError = err;
109 | } finally {
110 | try {
111 | if (!_iteratorNormalCompletion && _iterator["return"] != null) {
112 | _iterator["return"]();
113 | }
114 | } finally {
115 | if (_didIteratorError) {
116 | throw _iteratorError;
117 | }
118 | }
119 | }
120 |
121 | return css + mediaQueries;
122 | },
123 | clear: function clear() {
124 | var stylesheet = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : globalStylesheet;
125 | stylesheet.clear();
126 | return !stylesheet.size;
127 | },
128 | Map: Map,
129 | __stylesheet: globalStylesheet
130 | };
131 | exports["default"] = _default;
--------------------------------------------------------------------------------
/dist/utils/CSSProperty.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports.shorthandPropertyExpansions = exports.isUnitlessNumber = void 0;
7 |
8 | /**
9 | * Copyright 2013-present, Facebook, Inc.
10 | * All rights reserved.
11 | *
12 | * This source code is licensed under the BSD-style license found in the
13 | * LICENSE file in the root directory of this source tree. An additional grant
14 | * of patent rights can be found in the PATENTS file in the same directory.
15 | *
16 | * @providesModule CSSProperty
17 | */
18 |
19 | /**
20 | * CSS properties which accept numbers but are not in units of "px".
21 | */
22 | var isUnitlessNumber = {
23 | animationIterationCount: true,
24 | borderImageOutset: true,
25 | borderImageSlice: true,
26 | borderImageWidth: true,
27 | boxFlex: true,
28 | boxFlexGroup: true,
29 | boxOrdinalGroup: true,
30 | columnCount: true,
31 | flex: true,
32 | flexGrow: true,
33 | flexPositive: true,
34 | flexShrink: true,
35 | flexNegative: true,
36 | flexOrder: true,
37 | gridRow: true,
38 | gridColumn: true,
39 | fontWeight: true,
40 | lineClamp: true,
41 | lineHeight: true,
42 | opacity: true,
43 | order: true,
44 | orphans: true,
45 | tabSize: true,
46 | widows: true,
47 | zIndex: true,
48 | zoom: true,
49 | // SVG-related properties
50 | fillOpacity: true,
51 | floodOpacity: true,
52 | stopOpacity: true,
53 | strokeDasharray: true,
54 | strokeDashoffset: true,
55 | strokeMiterlimit: true,
56 | strokeOpacity: true,
57 | strokeWidth: true
58 | };
59 | /**
60 | * @param {string} prefix vendor-specific prefix, eg: Webkit
61 | * @param {string} key style name, eg: transitionDuration
62 | * @return {string} style name prefixed with `prefix`, properly camelCased, eg:
63 | * WebkitTransitionDuration
64 | */
65 |
66 | exports.isUnitlessNumber = isUnitlessNumber;
67 |
68 | function prefixKey(prefix, key) {
69 | return prefix + key.charAt(0).toUpperCase() + key.substring(1);
70 | }
71 | /**
72 | * Support style names that may come passed in prefixed by adding permutations
73 | * of vendor prefixes.
74 | */
75 |
76 |
77 | var prefixes = ['Webkit', 'ms', 'Moz', 'O']; // Using Object.keys here, or else the vanilla for-in loop makes IE8 go into an
78 | // infinite loop, because it iterates over the newly added props too.
79 |
80 | Object.keys(isUnitlessNumber).forEach(function (prop) {
81 | prefixes.forEach(function (prefix) {
82 | isUnitlessNumber[prefixKey(prefix, prop)] = isUnitlessNumber[prop];
83 | });
84 | });
85 | /**
86 | * Most style properties can be unset by doing .style[prop] = '' but IE8
87 | * doesn't like doing that with shorthand properties so for the properties that
88 | * IE8 breaks on, which are listed here, we instead unset each of the
89 | * individual properties. See http://bugs.jquery.com/ticket/12385.
90 | * The 4-value 'clock' properties like margin, padding, border-width seem to
91 | * behave without any problems. Curiously, list-style works too without any
92 | * special prodding.
93 | */
94 |
95 | var shorthandPropertyExpansions = {
96 | background: {
97 | backgroundAttachment: true,
98 | backgroundColor: true,
99 | backgroundImage: true,
100 | backgroundPositionX: true,
101 | backgroundPositionY: true,
102 | backgroundRepeat: true
103 | },
104 | backgroundPosition: {
105 | backgroundPositionX: true,
106 | backgroundPositionY: true
107 | },
108 | border: {
109 | borderWidth: true,
110 | borderStyle: true,
111 | borderColor: true
112 | },
113 | borderBottom: {
114 | borderBottomWidth: true,
115 | borderBottomStyle: true,
116 | borderBottomColor: true
117 | },
118 | borderLeft: {
119 | borderLeftWidth: true,
120 | borderLeftStyle: true,
121 | borderLeftColor: true
122 | },
123 | borderRight: {
124 | borderRightWidth: true,
125 | borderRightStyle: true,
126 | borderRightColor: true
127 | },
128 | borderTop: {
129 | borderTopWidth: true,
130 | borderTopStyle: true,
131 | borderTopColor: true
132 | },
133 | font: {
134 | fontStyle: true,
135 | fontVariant: true,
136 | fontWeight: true,
137 | fontSize: true,
138 | lineHeight: true,
139 | fontFamily: true
140 | },
141 | outline: {
142 | outlineWidth: true,
143 | outlineStyle: true,
144 | outlineColor: true
145 | }
146 | };
147 | exports.shorthandPropertyExpansions = shorthandPropertyExpansions;
--------------------------------------------------------------------------------
/dist/utils/CSSPropertyOperations.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4 |
5 | Object.defineProperty(exports, "__esModule", {
6 | value: true
7 | });
8 | exports.createMarkupForStyles = createMarkupForStyles;
9 | exports.setValueForStyles = setValueForStyles;
10 |
11 | var _CSSProperty = require("./CSSProperty");
12 |
13 | var _dangerousStyleValue = _interopRequireDefault(require("./dangerousStyleValue"));
14 |
15 | var _fbjs = require("./fbjs");
16 |
17 | /**
18 | * Copyright 2013-present, Facebook, Inc.
19 | * All rights reserved.
20 | *
21 | * This source code is licensed under the BSD-style license found in the
22 | * LICENSE file in the root directory of this source tree. An additional grant
23 | * of patent rights can be found in the PATENTS file in the same directory.
24 | *
25 | * @providesModule CSSPropertyOperations
26 | */
27 | // var ExecutionEnvironment = require('fbjs/lib/ExecutionEnvironment');
28 | // var camelizeStyleName = require('fbjs/lib/camelizeStyleName');
29 | // var hyphenateStyleName = require('fbjs/lib/hyphenateStyleName');
30 | // var memoizeStringOnly = require('fbjs/lib/memoizeStringOnly');
31 | // var warning = require('fbjs/lib/warning');
32 | var processStyleName = (0, _fbjs.memoizeStringOnly)(function (styleName) {
33 | return (0, _fbjs.hyphenateStyleName)(styleName);
34 | });
35 | var hasShorthandPropertyBug = false;
36 | var styleFloatAccessor = 'cssFloat';
37 |
38 | if (_fbjs.ExecutionEnvironment.canUseDOM) {
39 | var tempStyle = document.createElement('div').style;
40 |
41 | try {
42 | // IE8 throws "Invalid argument." if resetting shorthand style properties.
43 | tempStyle.font = '';
44 | } catch (e) {
45 | hasShorthandPropertyBug = true;
46 | } // IE8 only supports accessing cssFloat (standard) as styleFloat
47 |
48 |
49 | if (document.documentElement.style.cssFloat === undefined) {
50 | styleFloatAccessor = 'styleFloat';
51 | }
52 | }
53 |
54 | if (process.env.NODE_ENV !== 'production') {
55 | // 'msTransform' is correct, but the other prefixes should be capitalized
56 | var badVendoredStyleNamePattern = /^(?:webkit|moz|o)[A-Z]/; // style values shouldn't contain a semicolon
57 |
58 | var badStyleValueWithSemicolonPattern = /;\s*$/;
59 | var warnedStyleNames = {};
60 | var warnedStyleValues = {};
61 | var warnedForNaNValue = false;
62 |
63 | var warnHyphenatedStyleName = function warnHyphenatedStyleName(name, owner) {
64 | if (warnedStyleNames.hasOwnProperty(name) && warnedStyleNames[name]) {
65 | return;
66 | }
67 |
68 | warnedStyleNames[name] = true;
69 | (0, _fbjs.warning)(false, 'Unsupported style property %s. Did you mean %s?%s', name, (0, _fbjs.camelizeStyleName)(name), checkRenderMessage(owner));
70 | };
71 |
72 | var warnBadVendoredStyleName = function warnBadVendoredStyleName(name, owner) {
73 | if (warnedStyleNames.hasOwnProperty(name) && warnedStyleNames[name]) {
74 | return;
75 | }
76 |
77 | warnedStyleNames[name] = true;
78 | (0, _fbjs.warning)(false, 'Unsupported vendor-prefixed style property %s. Did you mean %s?%s', name, name.charAt(0).toUpperCase() + name.slice(1), checkRenderMessage(owner));
79 | };
80 |
81 | var warnStyleValueWithSemicolon = function warnStyleValueWithSemicolon(name, value, owner) {
82 | if (warnedStyleValues.hasOwnProperty(value) && warnedStyleValues[value]) {
83 | return;
84 | }
85 |
86 | warnedStyleValues[value] = true;
87 | (0, _fbjs.warning)(false, "Style property values shouldn't contain a semicolon.%s " + 'Try "%s: %s" instead.', checkRenderMessage(owner), name, value.replace(badStyleValueWithSemicolonPattern, ''));
88 | };
89 |
90 | var warnStyleValueIsNaN = function warnStyleValueIsNaN(name, value, owner) {
91 | if (warnedForNaNValue) {
92 | return;
93 | }
94 |
95 | warnedForNaNValue = true;
96 | (0, _fbjs.warning)(false, '`NaN` is an invalid value for the `%s` css style property.%s', name, checkRenderMessage(owner));
97 | };
98 |
99 | var checkRenderMessage = function checkRenderMessage(owner) {
100 | if (owner) {
101 | var name = owner.getName();
102 |
103 | if (name) {
104 | return ' Check the render method of `' + name + '`.';
105 | }
106 | }
107 |
108 | return '';
109 | };
110 | /**
111 | * @param {string} name
112 | * @param {*} value
113 | * @param {ReactDOMComponent} component
114 | */
115 |
116 |
117 | var warnValidStyle = function warnValidStyle(name, value, component) {
118 | var owner;
119 |
120 | if (component) {
121 | owner = component._currentElement._owner;
122 | }
123 |
124 | if (name.indexOf('-') > -1) {
125 | warnHyphenatedStyleName(name, owner);
126 | } else if (badVendoredStyleNamePattern.test(name)) {
127 | warnBadVendoredStyleName(name, owner);
128 | } else if (badStyleValueWithSemicolonPattern.test(value)) {
129 | warnStyleValueWithSemicolon(name, value, owner);
130 | }
131 |
132 | if (typeof value === 'number' && isNaN(value)) {
133 | warnStyleValueIsNaN(name, value, owner);
134 | }
135 | };
136 | }
137 | /**
138 | * Operations for dealing with CSS properties.
139 | */
140 |
141 | /**
142 | * Serializes a mapping of style properties for use as inline styles:
143 | *
144 | * > createMarkupForStyles({width: '200px', height: 0})
145 | * "width:200px;height:0;"
146 | *
147 | * Undefined values are ignored so that declarative programming is easier.
148 | * The result should be HTML-escaped before insertion into the DOM.
149 | *
150 | * @param {object} styles
151 | * @param {ReactDOMComponent} component
152 | * @return {?string}
153 | */
154 |
155 |
156 | function createMarkupForStyles(styles, component) {
157 | var serialized = '';
158 |
159 | for (var styleName in styles) {
160 | if (!styles.hasOwnProperty(styleName)) {
161 | continue;
162 | }
163 |
164 | var styleValue = styles[styleName];
165 |
166 | if (process.env.NODE_ENV !== 'production') {
167 | warnValidStyle(styleName, styleValue, component);
168 | }
169 |
170 | if (styleValue != null) {
171 | serialized += processStyleName(styleName) + ':';
172 | serialized += (0, _dangerousStyleValue["default"])(styleName, styleValue, component) + ';';
173 | }
174 | }
175 |
176 | return serialized || null;
177 | }
178 | /**
179 | * Sets the value for multiple styles on a node. If a value is specified as
180 | * '' (empty string), the corresponding style property will be unset.
181 | *
182 | * @param {DOMElement} node
183 | * @param {object} styles
184 | * @param {ReactDOMComponent} component
185 | */
186 |
187 |
188 | function setValueForStyles(node, styles, component) {
189 | var style = node.style;
190 |
191 | for (var styleName in styles) {
192 | if (!styles.hasOwnProperty(styleName)) {
193 | continue;
194 | }
195 |
196 | if (process.env.NODE_ENV !== 'production') {
197 | warnValidStyle(styleName, styles[styleName], component);
198 | }
199 |
200 | var styleValue = (0, _dangerousStyleValue["default"])(styleName, styles[styleName], component);
201 |
202 | if (styleName === 'float' || styleName === 'cssFloat') {
203 | styleName = styleFloatAccessor;
204 | }
205 |
206 | if (styleValue) {
207 | style[styleName] = styleValue;
208 | } else {
209 | var expansion = hasShorthandPropertyBug && _CSSProperty.shorthandPropertyExpansions[styleName];
210 |
211 | if (expansion) {
212 | // Shorthand property that IE8 won't like unsetting, so unset each
213 | // component to placate it
214 | for (var individualStyleName in expansion) {
215 | style[individualStyleName] = '';
216 | }
217 | } else {
218 | style[styleName] = '';
219 | }
220 | }
221 | }
222 | }
--------------------------------------------------------------------------------
/dist/utils/dangerousStyleValue.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports["default"] = dangerousStyleValue;
7 |
8 | var _CSSProperty = require("./CSSProperty");
9 |
10 | var _fbjs = require("./fbjs");
11 |
12 | /**
13 | * Copyright 2013-present, Facebook, Inc.
14 | * All rights reserved.
15 | *
16 | * This source code is licensed under the BSD-style license found in the
17 | * LICENSE file in the root directory of this source tree. An additional grant
18 | * of patent rights can be found in the PATENTS file in the same directory.
19 | *
20 | * @providesModule dangerousStyleValue
21 | */
22 | var styleWarnings = {};
23 | /**
24 | * Convert a value into the proper css writable value. The style name `name`
25 | * should be logical (no hyphens), as specified
26 | * in `CSSProperty.isUnitlessNumber`.
27 | *
28 | * @param {string} name CSS property name such as `topMargin`.
29 | * @param {*} value CSS property value such as `10px`.
30 | * @param {ReactDOMComponent} component
31 | * @return {string} Normalized style value with dimensions applied.
32 | */
33 |
34 | function dangerousStyleValue(name, value, component) {
35 | // Note that we've removed escapeTextForBrowser() calls here since the
36 | // whole string will be escaped when the attribute is injected into
37 | // the markup. If you provide unsafe user data here they can inject
38 | // arbitrary CSS which may be problematic (I couldn't repro this):
39 | // https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet
40 | // http://www.thespanner.co.uk/2007/11/26/ultimate-xss-css-injection/
41 | // This is not an XSS hole but instead a potential CSS injection issue
42 | // which has lead to a greater discussion about how we're going to
43 | // trust URLs moving forward. See #2115901
44 | var isEmpty = value == null || typeof value === 'boolean' || value === '';
45 |
46 | if (isEmpty) {
47 | return '';
48 | }
49 |
50 | var isNonNumeric = isNaN(value);
51 |
52 | if (isNonNumeric || value === 0 || _CSSProperty.isUnitlessNumber.hasOwnProperty(name) && _CSSProperty.isUnitlessNumber[name]) {
53 | return '' + value; // cast to string
54 | }
55 |
56 | if (typeof value === 'string') {
57 | if (process.env.NODE_ENV !== 'production') {
58 | if (component) {
59 | var owner = component._currentElement._owner;
60 | var ownerName = owner ? owner.getName() : null;
61 |
62 | if (ownerName && !styleWarnings[ownerName]) {
63 | styleWarnings[ownerName] = {};
64 | }
65 |
66 | var warned = false;
67 |
68 | if (ownerName) {
69 | var warnings = styleWarnings[ownerName];
70 | warned = warnings[name];
71 |
72 | if (!warned) {
73 | warnings[name] = true;
74 | }
75 | }
76 |
77 | if (!warned) {
78 | (0, _fbjs.warning)(false, 'a `%s` tag (owner: `%s`) was passed a numeric string value ' + 'for CSS property `%s` (value: `%s`) which will be treated ' + 'as a unitless number in a future version of React.', component._currentElement.type, ownerName || 'unknown', name, value);
79 | }
80 | }
81 | }
82 |
83 | value = value.trim();
84 | }
85 |
86 | return value + 'px';
87 | }
--------------------------------------------------------------------------------
/dist/utils/fbjs.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports.hyphenateStyleName = hyphenateStyleName;
7 | exports.camelizeStyleName = camelizeStyleName;
8 | exports.memoizeStringOnly = memoizeStringOnly;
9 | exports.warning = exports.ExecutionEnvironment = void 0;
10 |
11 | /**
12 | * Copyright (c) 2013-present, Facebook, Inc.
13 | * All rights reserved.
14 | *
15 | * This source code is licensed under the BSD-style license found in the
16 | * LICENSE file in the root directory of this source tree. An additional grant
17 | * of patent rights can be found in the PATENTS file in the same directory.
18 | */
19 | // ExecutionEnvironment.js
20 | var canUseDOM = !!(typeof window !== 'undefined' && window.document && window.document.createElement);
21 | /**
22 | * Simple, lightweight module assisting with the detection and context of
23 | * Worker. Helps avoid circular dependencies and allows code to reason about
24 | * whether or not they are in a Worker, even if they never include the main
25 | * `ReactWorker` dependency.
26 | */
27 |
28 | var ExecutionEnvironment = {
29 | canUseDOM: canUseDOM,
30 | canUseWorkers: typeof Worker !== 'undefined',
31 | canUseEventListeners: canUseDOM && !!(window.addEventListener || window.attachEvent),
32 | canUseViewport: canUseDOM && !!window.screen,
33 | isInWorker: !canUseDOM // For now, this is true - might change in the future.
34 |
35 | }; // ---------------------
36 | // hypenate.js
37 |
38 | exports.ExecutionEnvironment = ExecutionEnvironment;
39 | var _uppercasePattern = /([A-Z])/g;
40 | /**
41 | * Hyphenates a camelcased string, for example:
42 | *
43 | * > hyphenate('backgroundColor')
44 | * < "background-color"
45 | *
46 | * For CSS style names, use `hyphenateStyleName` instead which works properly
47 | * with all vendor prefixes, including `ms`.
48 | *
49 | * @param {string} string
50 | * @return {string}
51 | */
52 |
53 | function hyphenate(string) {
54 | return string.replace(_uppercasePattern, '-$1').toLowerCase();
55 | } // ---------------------
56 | // hyphenateStyleName.js
57 |
58 |
59 | var msPattern = /^ms-/;
60 | /**
61 | * Hyphenates a camelcased CSS property name, for example:
62 | *
63 | * > hyphenateStyleName('backgroundColor')
64 | * < "background-color"
65 | * > hyphenateStyleName('MozTransition')
66 | * < "-moz-transition"
67 | * > hyphenateStyleName('msTransition')
68 | * < "-ms-transition"
69 | *
70 | * As Modernizr suggests (http://modernizr.com/docs/#prefixed), an `ms` prefix
71 | * is converted to `-ms-`.
72 | *
73 | * @param {string} string
74 | * @return {string}
75 | */
76 |
77 | function hyphenateStyleName(string) {
78 | return hyphenate(string).replace(msPattern, '-ms-');
79 | } // ---------------------
80 | // camelize.js
81 |
82 |
83 | var _hyphenPattern = /-(.)/g;
84 | /**
85 | * Camelcases a hyphenated string, for example:
86 | *
87 | * > camelize('background-color')
88 | * < "backgroundColor"
89 | *
90 | * @param {string} string
91 | * @return {string}
92 | */
93 |
94 | function camelize(string) {
95 | return string.replace(_hyphenPattern, function (_, character) {
96 | return character.toUpperCase();
97 | });
98 | } // ---------------------
99 | // camelizeStyleName.js
100 |
101 |
102 | var dashMsPattern = /^-ms-/; // renamed from msPattern to avoid name conflict
103 |
104 | /**
105 | * Camelcases a hyphenated CSS property name, for example:
106 | *
107 | * > camelizeStyleName('background-color')
108 | * < "backgroundColor"
109 | * > camelizeStyleName('-moz-transition')
110 | * < "MozTransition"
111 | * > camelizeStyleName('-ms-transition')
112 | * < "msTransition"
113 | *
114 | * As Andi Smith suggests
115 | * (http://www.andismith.com/blog/2012/02/modernizr-prefixed/), an `-ms` prefix
116 | * is converted to lowercase `ms`.
117 | *
118 | * @param {string} string
119 | * @return {string}
120 | */
121 |
122 | function camelizeStyleName(string) {
123 | return camelize(string.replace(dashMsPattern, 'ms-'));
124 | } // ---------------------
125 | // memoizeStringOnly.js
126 |
127 | /**
128 | * Memoizes the return value of a function that accepts one string argument.
129 | */
130 |
131 |
132 | function memoizeStringOnly(callback) {
133 | var cache = {};
134 | return function (string) {
135 | if (!cache.hasOwnProperty(string)) {
136 | cache[string] = callback.call(this, string);
137 | }
138 |
139 | return cache[string];
140 | };
141 | } // ---------------------
142 | // warning.js
143 |
144 |
145 | var warning = function warning() {}; // if (__DEV__) {
146 | // function printWarning(format, ...args) {
147 | // var argIndex = 0;
148 | // var message = 'Warning: ' + format.replace(/%s/g, () => args[argIndex++]);
149 | // if (typeof console !== 'undefined') {
150 | // console.error(message);
151 | // }
152 | // try {
153 | // // --- Welcome to debugging React ---
154 | // // This error was thrown as a convenience so that you can use this stack
155 | // // to find the callsite that caused this warning to fire.
156 | // throw new Error(message);
157 | // } catch (x) {}
158 | // }
159 | //
160 | // warning = function(condition, format, ...args) {
161 | // if (format === undefined) {
162 | // throw new Error(
163 | // '`warning(condition, format, ...args)` requires a warning ' +
164 | // 'message argument'
165 | // );
166 | // }
167 | // if (!condition) {
168 | // printWarning(format, ...args);
169 | // }
170 | // };
171 | // }
172 |
173 |
174 | exports.warning = warning;
--------------------------------------------------------------------------------
/dist/utils/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4 |
5 | Object.defineProperty(exports, "__esModule", {
6 | value: true
7 | });
8 | exports.sortObject = sortObject;
9 | exports.createHash = createHash;
10 | exports.stringifyObject = stringifyObject;
11 | exports.extendedToString = extendedToString;
12 | exports.createClassName = createClassName;
13 | exports.createMarkup = createMarkup;
14 | exports.isEmpty = isEmpty;
15 | exports.isPseudo = isPseudo;
16 | exports.isMediaQuery = isMediaQuery;
17 | exports.seperateStyles = seperateStyles;
18 |
19 | var _typeof2 = _interopRequireDefault(require("@babel/runtime/helpers/typeof"));
20 |
21 | var _CSSPropertyOperations = require("./CSSPropertyOperations");
22 |
23 | function sortObject(obj) {
24 | return Object.keys(obj).sort().reduce(function (acc, key) {
25 | var val = obj[key];
26 | if (val || val === 0) acc[key] = val;
27 | return acc;
28 | }, {});
29 | }
30 |
31 | function createHash(str) {
32 | var i = str.length;
33 | if (i === 0) return 0;
34 | var hash = 5381;
35 |
36 | while (i) {
37 | hash = hash * 33 ^ str.charCodeAt(--i);
38 | }
39 |
40 | return hash >>> 0;
41 | }
42 |
43 | function stringifyObject(obj) {
44 | var keys = Object.keys(obj);
45 | var str = '';
46 |
47 | for (var i = 0, len = keys.length; i < len; i++) {
48 | str += keys[i] + obj[keys[i]];
49 | }
50 |
51 | return str;
52 | }
53 |
54 | var SYMBOL_SET = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
55 |
56 | function extendedToString(num, base) {
57 | var conversion = '';
58 | if (base > SYMBOL_SET.length || base <= 1 || !Number.isInteger(base)) throw new Error("".concat(base, " should be an integer between 1 and ").concat(SYMBOL_SET.length));
59 |
60 | while (num >= 1) {
61 | conversion = SYMBOL_SET[num - base * Math.floor(num / base)] + conversion;
62 | num = Math.floor(num / base);
63 | }
64 |
65 | return base < 11 ? parseInt(conversion) : conversion;
66 | }
67 |
68 | function createClassName(obj) {
69 | var hash = extendedToString(createHash(stringifyObject(obj)), 62);
70 | return hash ? '_' + hash : undefined;
71 | }
72 |
73 | function createMarkup(obj) {
74 | return (0, _CSSPropertyOperations.createMarkupForStyles)(obj);
75 | }
76 |
77 | function isEmpty(obj) {
78 | return !Object.keys(obj).length;
79 | }
80 |
81 | function isPseudo(_ref) {
82 | var style = _ref.style,
83 | rule = _ref.rule;
84 | return rule.charAt(0) === ':' && (0, _typeof2["default"])(style) === 'object';
85 | }
86 |
87 | function isMediaQuery(_ref2) {
88 | var style = _ref2.style,
89 | rule = _ref2.rule;
90 | return rule.charAt(0) === '@' && (0, _typeof2["default"])(style) === 'object';
91 | }
92 |
93 | function handle(type, acc, _ref3) {
94 | var style = _ref3.style,
95 | rule = _ref3.rule;
96 | var pseudos = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : [];
97 | var hash = createClassName(sortObject(style));
98 | var rules = pseudos.length ? [[].concat(rule, style, pseudos)] : rule;
99 | acc[type] = acc[type].concat(rules);
100 | acc.style[rule] = hash;
101 | return acc;
102 | }
103 |
104 | function seperateStyles(styles) {
105 | return Object.keys(styles).reduce(function (acc, rule) {
106 | var content = {
107 | style: styles[rule],
108 | rule: rule
109 | };
110 |
111 | if (isPseudo(content)) {
112 | return handle('pseudos', acc, content);
113 | }
114 |
115 | if (isMediaQuery(content)) {
116 | var _seperateStyles = seperateStyles(content.style),
117 | style = _seperateStyles.style,
118 | pseudos = _seperateStyles.pseudos;
119 |
120 | return handle('mediaQueries', acc, {
121 | rule: rule,
122 | style: style
123 | }, pseudos);
124 | }
125 |
126 | acc.style[rule] = content.style;
127 | return acc;
128 | }, {
129 | style: {},
130 | pseudos: [],
131 | mediaQueries: []
132 | });
133 | }
--------------------------------------------------------------------------------
/lib/__tests__/fixtures.js:
--------------------------------------------------------------------------------
1 | export const basic = {
2 | style1: {
3 | color: 'tomato',
4 | fontSize: '1em'
5 | },
6 | style2: {
7 | color: 'purple',
8 | backgroundColor: 'papayawhip'
9 | }
10 | };
11 |
12 | export const duplicates = {
13 | style1: {
14 | width: 10,
15 | height: 10
16 | },
17 | style2: {
18 | height: 10,
19 | width: 10,
20 | border: null
21 | },
22 | style3: {
23 | border: false,
24 | height: 10,
25 | width: 10
26 | },
27 | style4: {
28 | height: 10,
29 | border: undefined,
30 | width: 10
31 | }
32 | };
33 |
34 | export const pseudos = {
35 | style1: {
36 | border: 1
37 | },
38 | style2: {
39 | border: 1,
40 | ':hover': {
41 | color: 'tomato'
42 | }
43 | },
44 | style3: {
45 | border: 1,
46 | ':active': {
47 | color: 'purple'
48 | }
49 | }
50 | };
51 |
52 | export const mediaQueries = {
53 | style1: {
54 | color: 'red',
55 | '@media (max-width: 600px)': {
56 | color: 'blue'
57 | }
58 | },
59 | style2: {
60 | color: 'red',
61 | '@media (max-width: 600px)': {
62 | color: 'green'
63 | }
64 | }
65 | };
66 |
--------------------------------------------------------------------------------
/lib/__tests__/lib.test.js:
--------------------------------------------------------------------------------
1 | // Use plain old commonjs require to ensure it works everywhere...
2 | import StyleSheet from '../';
3 | import assert from 'assert';
4 | import { parse } from 'css';
5 | import * as style from './fixtures';
6 |
7 | const unique = arr => [...new Set(arr)];
8 | const values = obj => Object.keys(obj).map(key => obj[key]);
9 |
10 | describe('stilr', () => {
11 | it('exports the Map class used for making stylesheets', () => {
12 | assert.ok(StyleSheet.Map);
13 | });
14 |
15 | describe('#create(obj)', () => {
16 | beforeEach(() => {
17 | StyleSheet.clear();
18 | });
19 |
20 | it('contains create method', () => {
21 | assert.equal(typeof StyleSheet.create, 'function');
22 | });
23 |
24 | it('creates an entry in the stylesheet', () => {
25 | const entries = Object.keys(style.basic);
26 | StyleSheet.create(style.basic);
27 | assert.equal(StyleSheet.__stylesheet.size, entries.length);
28 | });
29 |
30 | it('returns an object', () => {
31 | const styles = StyleSheet.create(style.basic);
32 | assert.equal(typeof styles, 'object');
33 | });
34 |
35 | it('has the same keys', () => {
36 | const keys = Object.keys(StyleSheet.create(style.basic));
37 | const originalKeys = Object.keys(style.basic);
38 |
39 | assert.equal(keys.join(''), originalKeys.join(''));
40 | });
41 |
42 | it('assigns the same class name to duplicate styles', () => {
43 | const styles = StyleSheet.create(style.duplicates);
44 | const classNames = Object.keys(styles);
45 |
46 | classNames.map(className =>
47 | assert.equal(styles[classNames[0]], styles[className])
48 | );
49 | });
50 |
51 | it('handles pseudo classes', () => {
52 | const styles = StyleSheet.create(style.pseudos);
53 | assert.equal(
54 | unique(values(styles)).length,
55 | Object.keys(styles).length,
56 | `All classnames should be unique: .${values(styles).join(' .')}`
57 | );
58 | });
59 |
60 | it('handles media queries', () => {
61 | const styles = StyleSheet.create(style.mediaQueries);
62 |
63 | assert.equal(
64 | unique(values(styles)).length,
65 | Object.keys(styles).length,
66 | `All classnames should be unique: .${values(styles).join(' .')}`
67 | );
68 | });
69 |
70 | it('accepts a alternative stylesheet', () => {
71 | const altStylesheet = new Map();
72 | const entries = Object.keys(style.basic);
73 | StyleSheet.create(style.basic, altStylesheet);
74 |
75 | StyleSheet.create(style.basic);
76 | assert.equal(altStylesheet.size, entries.length);
77 |
78 | assert.notEqual(altStylesheet, StyleSheet.__stylesheet);
79 | });
80 |
81 | it('assigns unique class names', () => {
82 | const uniqueStyles = StyleSheet.create({
83 | unique1: {
84 | justifyContent: 'center'
85 | },
86 | unique2: {
87 | alignItems: 'center'
88 | },
89 | unique3: {
90 | textAlign: 'center'
91 | },
92 | unique4: {
93 | color: 'red',
94 | ':hover': {
95 | color: 'green'
96 | }
97 | },
98 | unique5: {
99 | color: 'red',
100 | ':active': {
101 | color: 'green'
102 | }
103 | }
104 | });
105 | const keys = Object.keys(uniqueStyles);
106 | const classes = unique(values(uniqueStyles));
107 |
108 | assert.equal(keys.length, classes.length);
109 | });
110 | });
111 |
112 | describe('#render()', () => {
113 | beforeEach(() => {
114 | StyleSheet.clear();
115 | });
116 |
117 | it('contains render method', () => {
118 | assert.equal(typeof StyleSheet.render, 'function');
119 | });
120 |
121 | it('returns a string', () => {
122 | StyleSheet.create(style.basic);
123 | const stylesheet = StyleSheet.render();
124 | assert.equal(typeof stylesheet, 'string');
125 | });
126 |
127 | it('is parseable css', () => {
128 | StyleSheet.create(style.basic);
129 | const parsedCss = parse(StyleSheet.render());
130 | assert.ok(parsedCss);
131 |
132 | const renderedClassNames = parsedCss.stylesheet.rules.map(
133 | rule => rule.selectors[0]
134 | );
135 |
136 | for (const className of StyleSheet.__stylesheet.keys()) {
137 | assert.ok(
138 | renderedClassNames.indexOf(`.${className}`) !== -1,
139 | `.${className} should be part of the rendered css classes: '${renderedClassNames}'`
140 | );
141 | }
142 | });
143 |
144 | it('outputs pseudo classes correctly', () => {
145 | const hover = /(\._[\s\S]+)(:hover{)/g;
146 |
147 | StyleSheet.create({
148 | style: {
149 | color: 'tomato',
150 | ':hover': {
151 | color: 'red'
152 | }
153 | }
154 | });
155 |
156 | const css = StyleSheet.render();
157 | assert.ok(css.match(hover).length === 1);
158 | });
159 |
160 | it('outputs media queries correctly', () => {
161 | const breakpoint = '@media (max-width: 600px)';
162 | const mediaQueryStyles = {
163 | style1: {
164 | color: 'blue',
165 | [breakpoint]: {
166 | color: 'red'
167 | }
168 | },
169 | style2: {
170 | color: 'green',
171 | [breakpoint]: {
172 | color: 'blue'
173 | }
174 | }
175 | };
176 |
177 | StyleSheet.create(mediaQueryStyles);
178 |
179 | const css = StyleSheet.render();
180 | const rules = parse(css).stylesheet.rules;
181 | assert.equal(
182 | rules[0].type,
183 | 'rule',
184 | 'Rule should be before media queries'
185 | );
186 | assert.equal(rules[2].type, 'media');
187 | assert.equal(rules[2].media, breakpoint.replace('@media ', ''));
188 |
189 | const mediaQueryContent = rules[2].rules;
190 | assert.equal(
191 | mediaQueryContent.length,
192 | 2,
193 | 'Media query should contain two rules'
194 | );
195 |
196 | const mediaQueryStyleEntry1 = mediaQueryContent[0].declarations[0];
197 | assert.equal(
198 | mediaQueryStyleEntry1.value,
199 | mediaQueryStyles.style1[breakpoint][mediaQueryStyleEntry1.property]
200 | );
201 |
202 | const mediaQueryStyleEntry2 = mediaQueryContent[1].declarations[0];
203 | assert.equal(
204 | mediaQueryStyleEntry2.value,
205 | mediaQueryStyles.style2[breakpoint][mediaQueryStyleEntry2.property]
206 | );
207 | });
208 |
209 | it('should not output empty styles', () => {
210 | const breakpoint = '@media (max-width: 600px)';
211 | const emptyStyles = StyleSheet.create({
212 | beforeBreakpoint: {
213 | [breakpoint]: {
214 | color: 'red'
215 | }
216 | },
217 | withEmptyPseudo: {
218 | color: 'red',
219 | ':hover': {}
220 | },
221 | all: {}
222 | });
223 |
224 | const css = StyleSheet.render();
225 | const parsedCss = parse(css);
226 |
227 | assert.equal(
228 | emptyStyles.all,
229 | '',
230 | 'should return empty class string when style is empty'
231 | );
232 | assert.equal(
233 | parsedCss.stylesheet.rules.length,
234 | 2,
235 | 'stylesheet should contain two rules'
236 | );
237 | assert.equal(
238 | parsedCss.stylesheet.rules[1].rules.length,
239 | 1,
240 | '[beforeBreakpoint]-media-query should contain one rule'
241 | );
242 | });
243 |
244 | it('handles nested pseudo selectors in media queries', () => {
245 | const breakpoint = '@media (max-width: 600px)';
246 | const styles = {
247 | base: {
248 | color: 'red',
249 | ':hover': {
250 | color: 'blue'
251 | },
252 | ':active': {
253 | color: 'papayawhip'
254 | },
255 | [breakpoint]: {
256 | color: 'orange',
257 | ':hover': {
258 | color: 'green'
259 | },
260 | ':active': {
261 | color: 'tomato'
262 | }
263 | }
264 | }
265 | };
266 |
267 | StyleSheet.create(styles);
268 | const css = StyleSheet.render({ pretty: true });
269 | const { rules } = parse(css).stylesheet;
270 |
271 | assert.equal(rules[0].declarations[0].value, styles.base.color);
272 | assert.equal(rules[1].declarations[0].value, styles.base[':hover'].color);
273 | assert.equal(
274 | rules[2].declarations[0].value,
275 | styles.base[':active'].color
276 | );
277 |
278 | // Media Query
279 | assert.equal(rules[3].type, 'media');
280 | const mediaRules = rules[3].rules;
281 | assert.equal(
282 | mediaRules[0].declarations[0].value,
283 | styles.base[breakpoint].color
284 | );
285 | assert.equal(
286 | mediaRules[1].declarations[0].value,
287 | styles.base[breakpoint][':hover'].color
288 | );
289 | assert.equal(
290 | mediaRules[2].declarations[0].value,
291 | styles.base[breakpoint][':active'].color
292 | );
293 | });
294 |
295 | it('handles values as arrays', () => {
296 | const styles = {
297 | base: {
298 | boxShadow: ['1px 1px black', '0 0 5px red']
299 | }
300 | };
301 | StyleSheet.create(styles);
302 |
303 | const css = StyleSheet.render({ pretty: true });
304 | const { rules } = parse(css).stylesheet;
305 | assert.equal(
306 | rules[0].declarations[0].value,
307 | styles.base.boxShadow.join(',')
308 | );
309 | });
310 | });
311 |
312 | describe('#clear()', () => {
313 | beforeEach(() => {
314 | StyleSheet.clear();
315 | });
316 |
317 | it('contains clear method', () => {
318 | assert.equal(typeof StyleSheet.clear, 'function');
319 | });
320 |
321 | it('clears the stylesheet store', () => {
322 | StyleSheet.create(style.basic);
323 | assert.equal(StyleSheet.clear(), true);
324 | assert.equal(StyleSheet.__stylesheet.size, 0);
325 | });
326 | });
327 | });
328 |
--------------------------------------------------------------------------------
/lib/__tests__/utils.test.js:
--------------------------------------------------------------------------------
1 | import assert from 'assert';
2 | import * as utils from '../utils';
3 |
4 | describe('utils', () => {
5 | describe('#sortObject(obj)', () => {
6 | it('removes falsy', () => {
7 | const fixture = {
8 | truthy: '.',
9 | zero: 0,
10 | nany: NaN,
11 | nully: null,
12 | falsy: false,
13 | undefiny: undefined
14 | };
15 |
16 | const sorted = utils.sortObject(fixture);
17 |
18 | assert.equal(
19 | JSON.stringify(sorted),
20 | JSON.stringify({ truthy: '.', zero: 0 })
21 | );
22 | });
23 |
24 | it('returns objects sorted by key', () => {
25 | const obj1 = { a: '.', b: '.', c: '.', d: '.' };
26 | const obj2 = { d: '.', c: '.', b: '.', a: '.' };
27 |
28 | const sorted1 = utils.sortObject(obj1);
29 | const sorted2 = utils.sortObject(obj2);
30 |
31 | assert.equal(
32 | Object.keys(sorted1).join(''),
33 | Object.keys(sorted2).join('')
34 | );
35 | });
36 | });
37 |
38 | const STYLES = {
39 | width: 10,
40 | height: 10,
41 | color: 'tomato'
42 | };
43 | const STYLES_CSS = 'width:10px;height:10px;color:tomato;';
44 |
45 | describe('#createClassName(obj)', () => {
46 | const className = utils.createClassName(STYLES);
47 |
48 | it('retuns a class string', () => {
49 | assert.equal(
50 | typeof className,
51 | 'string',
52 | `${className} , should be string`
53 | );
54 | assert.equal(className.charAt(0), '_');
55 | });
56 | });
57 |
58 | describe('#createMarkup(obj)', () => {
59 | const markup = utils.createMarkup(STYLES);
60 |
61 | it('returns a css string', () => {
62 | assert.equal(typeof markup, 'string', `${markup} , should be string`);
63 | assert.equal(markup, STYLES_CSS);
64 | });
65 | });
66 | });
67 |
--------------------------------------------------------------------------------
/lib/index.js:
--------------------------------------------------------------------------------
1 | import {
2 | sortObject,
3 | createClassName,
4 | createMarkup,
5 | seperateStyles,
6 | isEmpty
7 | } from './utils';
8 |
9 | const globalStylesheet = new Map();
10 |
11 | export default {
12 | create(styles, stylesheet = globalStylesheet) {
13 | if (!(stylesheet instanceof Map))
14 | throw new Error(`${stylesheet} should be a Map`);
15 |
16 | return Object.keys(styles).reduce((acc, key) => {
17 | const { style, pseudos, mediaQueries } = seperateStyles(styles[key]);
18 | const className = createClassName(sortObject(style));
19 |
20 | if (className === undefined) {
21 | acc[key] = '';
22 | return acc;
23 | }
24 |
25 | if (!stylesheet.has(className)) stylesheet.set(className, style);
26 |
27 | if (pseudos.length) {
28 | pseudos.map(selector => {
29 | delete style[selector];
30 | const pseudoClassName = `${className}${selector}`;
31 |
32 | if (stylesheet.has(pseudoClassName)) return false;
33 |
34 | stylesheet.set(pseudoClassName, styles[key][selector]);
35 | });
36 | }
37 |
38 | if (mediaQueries.length) {
39 | mediaQueries.map(selector => {
40 | let mqSelector = selector;
41 | let mqStyles = styles[key][selector];
42 | let mqPseudos = [];
43 | let mqStylesheet;
44 |
45 | if (Array.isArray(selector)) {
46 | mqSelector = selector[0];
47 | mqStyles = selector[1];
48 | mqPseudos = selector.slice(2);
49 | }
50 |
51 | delete style[mqSelector];
52 |
53 | if (stylesheet.has(mqSelector)) {
54 | mqStylesheet = stylesheet.get(mqSelector);
55 |
56 | if (mqStylesheet.has(className)) return false;
57 | }
58 |
59 | mqStylesheet =
60 | mqStylesheet ||
61 | stylesheet.set(mqSelector, new Map()).get(mqSelector);
62 |
63 | mqStylesheet.set(className, mqStyles);
64 |
65 | if (mqPseudos.length) {
66 | mqPseudos.map(pseudo => {
67 | delete mqStyles[pseudo];
68 | const pseudoClassName = `${className}${pseudo}`;
69 |
70 | if (mqStylesheet.has(pseudoClassName)) return false;
71 | mqStylesheet.set(
72 | pseudoClassName,
73 | styles[key][mqSelector][pseudo]
74 | );
75 | });
76 | }
77 | });
78 | }
79 |
80 | acc[key] = className;
81 | return acc;
82 | }, {});
83 | },
84 |
85 | render(options = { pretty: false }, stylesheet = globalStylesheet) {
86 | const stylesheetEntries = stylesheet.entries();
87 | let css = '';
88 | let mediaQueries = '';
89 |
90 | for (const entry of stylesheetEntries) {
91 | const className = entry[0];
92 | const styles = entry[1];
93 | const isMap = styles instanceof Map;
94 |
95 | if (!isMap && isEmpty(styles)) continue;
96 |
97 | if (isMap) {
98 | const mediaQueryCSS = this.render(options, stylesheet.get(className));
99 | mediaQueries += options.pretty
100 | ? `${className} {\n${mediaQueryCSS}}\n`
101 | : `${className}{${mediaQueryCSS}}`;
102 | continue;
103 | }
104 |
105 | const markup = createMarkup(styles);
106 | css += options.pretty
107 | ? `.${className} {\n${markup.split(';').join(';\n')}}\n`
108 | : `.${className}{${markup}}`;
109 | }
110 |
111 | return css + mediaQueries;
112 | },
113 |
114 | clear(stylesheet = globalStylesheet) {
115 | stylesheet.clear();
116 | return !stylesheet.size;
117 | },
118 |
119 | Map,
120 |
121 | __stylesheet: globalStylesheet
122 | };
123 |
--------------------------------------------------------------------------------
/lib/utils/CSSProperty.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2013-present, Facebook, Inc.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under the BSD-style license found in the
6 | * LICENSE file in the root directory of this source tree. An additional grant
7 | * of patent rights can be found in the PATENTS file in the same directory.
8 | *
9 | * @providesModule CSSProperty
10 | */
11 |
12 | /**
13 | * CSS properties which accept numbers but are not in units of "px".
14 | */
15 |
16 | export const isUnitlessNumber = {
17 | animationIterationCount: true,
18 | borderImageOutset: true,
19 | borderImageSlice: true,
20 | borderImageWidth: true,
21 | boxFlex: true,
22 | boxFlexGroup: true,
23 | boxOrdinalGroup: true,
24 | columnCount: true,
25 | flex: true,
26 | flexGrow: true,
27 | flexPositive: true,
28 | flexShrink: true,
29 | flexNegative: true,
30 | flexOrder: true,
31 | gridRow: true,
32 | gridColumn: true,
33 | fontWeight: true,
34 | lineClamp: true,
35 | lineHeight: true,
36 | opacity: true,
37 | order: true,
38 | orphans: true,
39 | tabSize: true,
40 | widows: true,
41 | zIndex: true,
42 | zoom: true,
43 |
44 | // SVG-related properties
45 | fillOpacity: true,
46 | floodOpacity: true,
47 | stopOpacity: true,
48 | strokeDasharray: true,
49 | strokeDashoffset: true,
50 | strokeMiterlimit: true,
51 | strokeOpacity: true,
52 | strokeWidth: true
53 | };
54 |
55 | /**
56 | * @param {string} prefix vendor-specific prefix, eg: Webkit
57 | * @param {string} key style name, eg: transitionDuration
58 | * @return {string} style name prefixed with `prefix`, properly camelCased, eg:
59 | * WebkitTransitionDuration
60 | */
61 | function prefixKey(prefix, key) {
62 | return prefix + key.charAt(0).toUpperCase() + key.substring(1);
63 | }
64 |
65 | /**
66 | * Support style names that may come passed in prefixed by adding permutations
67 | * of vendor prefixes.
68 | */
69 | const prefixes = ['Webkit', 'ms', 'Moz', 'O'];
70 |
71 | // Using Object.keys here, or else the vanilla for-in loop makes IE8 go into an
72 | // infinite loop, because it iterates over the newly added props too.
73 | Object.keys(isUnitlessNumber).forEach(function(prop) {
74 | prefixes.forEach(function(prefix) {
75 | isUnitlessNumber[prefixKey(prefix, prop)] = isUnitlessNumber[prop];
76 | });
77 | });
78 |
79 | /**
80 | * Most style properties can be unset by doing .style[prop] = '' but IE8
81 | * doesn't like doing that with shorthand properties so for the properties that
82 | * IE8 breaks on, which are listed here, we instead unset each of the
83 | * individual properties. See http://bugs.jquery.com/ticket/12385.
84 | * The 4-value 'clock' properties like margin, padding, border-width seem to
85 | * behave without any problems. Curiously, list-style works too without any
86 | * special prodding.
87 | */
88 | export const shorthandPropertyExpansions = {
89 | background: {
90 | backgroundAttachment: true,
91 | backgroundColor: true,
92 | backgroundImage: true,
93 | backgroundPositionX: true,
94 | backgroundPositionY: true,
95 | backgroundRepeat: true
96 | },
97 | backgroundPosition: {
98 | backgroundPositionX: true,
99 | backgroundPositionY: true
100 | },
101 | border: {
102 | borderWidth: true,
103 | borderStyle: true,
104 | borderColor: true
105 | },
106 | borderBottom: {
107 | borderBottomWidth: true,
108 | borderBottomStyle: true,
109 | borderBottomColor: true
110 | },
111 | borderLeft: {
112 | borderLeftWidth: true,
113 | borderLeftStyle: true,
114 | borderLeftColor: true
115 | },
116 | borderRight: {
117 | borderRightWidth: true,
118 | borderRightStyle: true,
119 | borderRightColor: true
120 | },
121 | borderTop: {
122 | borderTopWidth: true,
123 | borderTopStyle: true,
124 | borderTopColor: true
125 | },
126 | font: {
127 | fontStyle: true,
128 | fontVariant: true,
129 | fontWeight: true,
130 | fontSize: true,
131 | lineHeight: true,
132 | fontFamily: true
133 | },
134 | outline: {
135 | outlineWidth: true,
136 | outlineStyle: true,
137 | outlineColor: true
138 | }
139 | };
140 |
--------------------------------------------------------------------------------
/lib/utils/CSSPropertyOperations.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2013-present, Facebook, Inc.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under the BSD-style license found in the
6 | * LICENSE file in the root directory of this source tree. An additional grant
7 | * of patent rights can be found in the PATENTS file in the same directory.
8 | *
9 | * @providesModule CSSPropertyOperations
10 | */
11 |
12 | import { shorthandPropertyExpansions } from './CSSProperty';
13 | import dangerousStyleValue from './dangerousStyleValue';
14 |
15 | import {
16 | ExecutionEnvironment,
17 | camelizeStyleName,
18 | hyphenateStyleName,
19 | memoizeStringOnly,
20 | warning
21 | } from './fbjs';
22 |
23 | // var ExecutionEnvironment = require('fbjs/lib/ExecutionEnvironment');
24 | // var camelizeStyleName = require('fbjs/lib/camelizeStyleName');
25 | // var hyphenateStyleName = require('fbjs/lib/hyphenateStyleName');
26 | // var memoizeStringOnly = require('fbjs/lib/memoizeStringOnly');
27 | // var warning = require('fbjs/lib/warning');
28 |
29 | var processStyleName = memoizeStringOnly(function(styleName) {
30 | return hyphenateStyleName(styleName);
31 | });
32 |
33 | var hasShorthandPropertyBug = false;
34 | var styleFloatAccessor = 'cssFloat';
35 | if (ExecutionEnvironment.canUseDOM) {
36 | var tempStyle = document.createElement('div').style;
37 | try {
38 | // IE8 throws "Invalid argument." if resetting shorthand style properties.
39 | tempStyle.font = '';
40 | } catch (e) {
41 | hasShorthandPropertyBug = true;
42 | }
43 | // IE8 only supports accessing cssFloat (standard) as styleFloat
44 | if (document.documentElement.style.cssFloat === undefined) {
45 | styleFloatAccessor = 'styleFloat';
46 | }
47 | }
48 |
49 | if (process.env.NODE_ENV !== 'production') {
50 | // 'msTransform' is correct, but the other prefixes should be capitalized
51 | var badVendoredStyleNamePattern = /^(?:webkit|moz|o)[A-Z]/;
52 |
53 | // style values shouldn't contain a semicolon
54 | var badStyleValueWithSemicolonPattern = /;\s*$/;
55 |
56 | var warnedStyleNames = {};
57 | var warnedStyleValues = {};
58 | var warnedForNaNValue = false;
59 |
60 | var warnHyphenatedStyleName = function(name, owner) {
61 | if (warnedStyleNames.hasOwnProperty(name) && warnedStyleNames[name]) {
62 | return;
63 | }
64 |
65 | warnedStyleNames[name] = true;
66 | warning(
67 | false,
68 | 'Unsupported style property %s. Did you mean %s?%s',
69 | name,
70 | camelizeStyleName(name),
71 | checkRenderMessage(owner)
72 | );
73 | };
74 |
75 | var warnBadVendoredStyleName = function(name, owner) {
76 | if (warnedStyleNames.hasOwnProperty(name) && warnedStyleNames[name]) {
77 | return;
78 | }
79 |
80 | warnedStyleNames[name] = true;
81 | warning(
82 | false,
83 | 'Unsupported vendor-prefixed style property %s. Did you mean %s?%s',
84 | name,
85 | name.charAt(0).toUpperCase() + name.slice(1),
86 | checkRenderMessage(owner)
87 | );
88 | };
89 |
90 | var warnStyleValueWithSemicolon = function(name, value, owner) {
91 | if (warnedStyleValues.hasOwnProperty(value) && warnedStyleValues[value]) {
92 | return;
93 | }
94 |
95 | warnedStyleValues[value] = true;
96 | warning(
97 | false,
98 | "Style property values shouldn't contain a semicolon.%s " +
99 | 'Try "%s: %s" instead.',
100 | checkRenderMessage(owner),
101 | name,
102 | value.replace(badStyleValueWithSemicolonPattern, '')
103 | );
104 | };
105 |
106 | var warnStyleValueIsNaN = function(name, value, owner) {
107 | if (warnedForNaNValue) {
108 | return;
109 | }
110 |
111 | warnedForNaNValue = true;
112 | warning(
113 | false,
114 | '`NaN` is an invalid value for the `%s` css style property.%s',
115 | name,
116 | checkRenderMessage(owner)
117 | );
118 | };
119 |
120 | var checkRenderMessage = function(owner) {
121 | if (owner) {
122 | var name = owner.getName();
123 | if (name) {
124 | return ' Check the render method of `' + name + '`.';
125 | }
126 | }
127 | return '';
128 | };
129 |
130 | /**
131 | * @param {string} name
132 | * @param {*} value
133 | * @param {ReactDOMComponent} component
134 | */
135 | var warnValidStyle = function(name, value, component) {
136 | var owner;
137 | if (component) {
138 | owner = component._currentElement._owner;
139 | }
140 | if (name.indexOf('-') > -1) {
141 | warnHyphenatedStyleName(name, owner);
142 | } else if (badVendoredStyleNamePattern.test(name)) {
143 | warnBadVendoredStyleName(name, owner);
144 | } else if (badStyleValueWithSemicolonPattern.test(value)) {
145 | warnStyleValueWithSemicolon(name, value, owner);
146 | }
147 |
148 | if (typeof value === 'number' && isNaN(value)) {
149 | warnStyleValueIsNaN(name, value, owner);
150 | }
151 | };
152 | }
153 |
154 | /**
155 | * Operations for dealing with CSS properties.
156 | */
157 |
158 | /**
159 | * Serializes a mapping of style properties for use as inline styles:
160 | *
161 | * > createMarkupForStyles({width: '200px', height: 0})
162 | * "width:200px;height:0;"
163 | *
164 | * Undefined values are ignored so that declarative programming is easier.
165 | * The result should be HTML-escaped before insertion into the DOM.
166 | *
167 | * @param {object} styles
168 | * @param {ReactDOMComponent} component
169 | * @return {?string}
170 | */
171 | export function createMarkupForStyles(styles, component) {
172 | var serialized = '';
173 | for (var styleName in styles) {
174 | if (!styles.hasOwnProperty(styleName)) {
175 | continue;
176 | }
177 | var styleValue = styles[styleName];
178 | if (process.env.NODE_ENV !== 'production') {
179 | warnValidStyle(styleName, styleValue, component);
180 | }
181 | if (styleValue != null) {
182 | serialized += processStyleName(styleName) + ':';
183 | serialized += dangerousStyleValue(styleName, styleValue, component) + ';';
184 | }
185 | }
186 | return serialized || null;
187 | }
188 |
189 | /**
190 | * Sets the value for multiple styles on a node. If a value is specified as
191 | * '' (empty string), the corresponding style property will be unset.
192 | *
193 | * @param {DOMElement} node
194 | * @param {object} styles
195 | * @param {ReactDOMComponent} component
196 | */
197 | export function setValueForStyles(node, styles, component) {
198 | var style = node.style;
199 | for (var styleName in styles) {
200 | if (!styles.hasOwnProperty(styleName)) {
201 | continue;
202 | }
203 | if (process.env.NODE_ENV !== 'production') {
204 | warnValidStyle(styleName, styles[styleName], component);
205 | }
206 | var styleValue = dangerousStyleValue(
207 | styleName,
208 | styles[styleName],
209 | component
210 | );
211 | if (styleName === 'float' || styleName === 'cssFloat') {
212 | styleName = styleFloatAccessor;
213 | }
214 | if (styleValue) {
215 | style[styleName] = styleValue;
216 | } else {
217 | var expansion =
218 | hasShorthandPropertyBug && shorthandPropertyExpansions[styleName];
219 | if (expansion) {
220 | // Shorthand property that IE8 won't like unsetting, so unset each
221 | // component to placate it
222 | for (var individualStyleName in expansion) {
223 | style[individualStyleName] = '';
224 | }
225 | } else {
226 | style[styleName] = '';
227 | }
228 | }
229 | }
230 | }
231 |
--------------------------------------------------------------------------------
/lib/utils/dangerousStyleValue.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2013-present, Facebook, Inc.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under the BSD-style license found in the
6 | * LICENSE file in the root directory of this source tree. An additional grant
7 | * of patent rights can be found in the PATENTS file in the same directory.
8 | *
9 | * @providesModule dangerousStyleValue
10 | */
11 |
12 | import { isUnitlessNumber } from './CSSProperty';
13 | import { warning } from './fbjs';
14 |
15 | const styleWarnings = {};
16 |
17 | /**
18 | * Convert a value into the proper css writable value. The style name `name`
19 | * should be logical (no hyphens), as specified
20 | * in `CSSProperty.isUnitlessNumber`.
21 | *
22 | * @param {string} name CSS property name such as `topMargin`.
23 | * @param {*} value CSS property value such as `10px`.
24 | * @param {ReactDOMComponent} component
25 | * @return {string} Normalized style value with dimensions applied.
26 | */
27 | export default function dangerousStyleValue(name, value, component) {
28 | // Note that we've removed escapeTextForBrowser() calls here since the
29 | // whole string will be escaped when the attribute is injected into
30 | // the markup. If you provide unsafe user data here they can inject
31 | // arbitrary CSS which may be problematic (I couldn't repro this):
32 | // https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet
33 | // http://www.thespanner.co.uk/2007/11/26/ultimate-xss-css-injection/
34 | // This is not an XSS hole but instead a potential CSS injection issue
35 | // which has lead to a greater discussion about how we're going to
36 | // trust URLs moving forward. See #2115901
37 |
38 | const isEmpty = value == null || typeof value === 'boolean' || value === '';
39 | if (isEmpty) {
40 | return '';
41 | }
42 |
43 | const isNonNumeric = isNaN(value);
44 | if (
45 | isNonNumeric ||
46 | value === 0 ||
47 | (isUnitlessNumber.hasOwnProperty(name) && isUnitlessNumber[name])
48 | ) {
49 | return '' + value; // cast to string
50 | }
51 |
52 | if (typeof value === 'string') {
53 | if (process.env.NODE_ENV !== 'production') {
54 | if (component) {
55 | var owner = component._currentElement._owner;
56 | var ownerName = owner ? owner.getName() : null;
57 | if (ownerName && !styleWarnings[ownerName]) {
58 | styleWarnings[ownerName] = {};
59 | }
60 | var warned = false;
61 | if (ownerName) {
62 | var warnings = styleWarnings[ownerName];
63 | warned = warnings[name];
64 | if (!warned) {
65 | warnings[name] = true;
66 | }
67 | }
68 | if (!warned) {
69 | warning(
70 | false,
71 | 'a `%s` tag (owner: `%s`) was passed a numeric string value ' +
72 | 'for CSS property `%s` (value: `%s`) which will be treated ' +
73 | 'as a unitless number in a future version of React.',
74 | component._currentElement.type,
75 | ownerName || 'unknown',
76 | name,
77 | value
78 | );
79 | }
80 | }
81 | }
82 | value = value.trim();
83 | }
84 | return value + 'px';
85 | }
86 |
--------------------------------------------------------------------------------
/lib/utils/fbjs.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2013-present, Facebook, Inc.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under the BSD-style license found in the
6 | * LICENSE file in the root directory of this source tree. An additional grant
7 | * of patent rights can be found in the PATENTS file in the same directory.
8 | */
9 |
10 | // ExecutionEnvironment.js
11 |
12 | const canUseDOM = !!(
13 | typeof window !== 'undefined' &&
14 | window.document &&
15 | window.document.createElement
16 | );
17 |
18 | /**
19 | * Simple, lightweight module assisting with the detection and context of
20 | * Worker. Helps avoid circular dependencies and allows code to reason about
21 | * whether or not they are in a Worker, even if they never include the main
22 | * `ReactWorker` dependency.
23 | */
24 | export const ExecutionEnvironment = {
25 | canUseDOM: canUseDOM,
26 |
27 | canUseWorkers: typeof Worker !== 'undefined',
28 |
29 | canUseEventListeners:
30 | canUseDOM && !!(window.addEventListener || window.attachEvent),
31 |
32 | canUseViewport: canUseDOM && !!window.screen,
33 |
34 | isInWorker: !canUseDOM // For now, this is true - might change in the future.
35 | };
36 |
37 | // ---------------------
38 |
39 | // hypenate.js
40 |
41 | const _uppercasePattern = /([A-Z])/g;
42 |
43 | /**
44 | * Hyphenates a camelcased string, for example:
45 | *
46 | * > hyphenate('backgroundColor')
47 | * < "background-color"
48 | *
49 | * For CSS style names, use `hyphenateStyleName` instead which works properly
50 | * with all vendor prefixes, including `ms`.
51 | *
52 | * @param {string} string
53 | * @return {string}
54 | */
55 | function hyphenate(string) {
56 | return string.replace(_uppercasePattern, '-$1').toLowerCase();
57 | }
58 |
59 | // ---------------------
60 |
61 | // hyphenateStyleName.js
62 |
63 | const msPattern = /^ms-/;
64 |
65 | /**
66 | * Hyphenates a camelcased CSS property name, for example:
67 | *
68 | * > hyphenateStyleName('backgroundColor')
69 | * < "background-color"
70 | * > hyphenateStyleName('MozTransition')
71 | * < "-moz-transition"
72 | * > hyphenateStyleName('msTransition')
73 | * < "-ms-transition"
74 | *
75 | * As Modernizr suggests (http://modernizr.com/docs/#prefixed), an `ms` prefix
76 | * is converted to `-ms-`.
77 | *
78 | * @param {string} string
79 | * @return {string}
80 | */
81 | export function hyphenateStyleName(string) {
82 | return hyphenate(string).replace(msPattern, '-ms-');
83 | }
84 |
85 | // ---------------------
86 |
87 | // camelize.js
88 |
89 | const _hyphenPattern = /-(.)/g;
90 |
91 | /**
92 | * Camelcases a hyphenated string, for example:
93 | *
94 | * > camelize('background-color')
95 | * < "backgroundColor"
96 | *
97 | * @param {string} string
98 | * @return {string}
99 | */
100 | function camelize(string) {
101 | return string.replace(_hyphenPattern, function(_, character) {
102 | return character.toUpperCase();
103 | });
104 | }
105 |
106 | // ---------------------
107 |
108 | // camelizeStyleName.js
109 |
110 | const dashMsPattern = /^-ms-/; // renamed from msPattern to avoid name conflict
111 |
112 | /**
113 | * Camelcases a hyphenated CSS property name, for example:
114 | *
115 | * > camelizeStyleName('background-color')
116 | * < "backgroundColor"
117 | * > camelizeStyleName('-moz-transition')
118 | * < "MozTransition"
119 | * > camelizeStyleName('-ms-transition')
120 | * < "msTransition"
121 | *
122 | * As Andi Smith suggests
123 | * (http://www.andismith.com/blog/2012/02/modernizr-prefixed/), an `-ms` prefix
124 | * is converted to lowercase `ms`.
125 | *
126 | * @param {string} string
127 | * @return {string}
128 | */
129 | export function camelizeStyleName(string) {
130 | return camelize(string.replace(dashMsPattern, 'ms-'));
131 | }
132 |
133 | // ---------------------
134 |
135 | // memoizeStringOnly.js
136 |
137 | /**
138 | * Memoizes the return value of a function that accepts one string argument.
139 | */
140 | export function memoizeStringOnly(callback) {
141 | const cache = {};
142 | return function(string) {
143 | if (!cache.hasOwnProperty(string)) {
144 | cache[string] = callback.call(this, string);
145 | }
146 | return cache[string];
147 | };
148 | }
149 |
150 | // ---------------------
151 |
152 | // warning.js
153 |
154 | export const warning = function() {};
155 |
156 | // if (__DEV__) {
157 | // function printWarning(format, ...args) {
158 | // var argIndex = 0;
159 | // var message = 'Warning: ' + format.replace(/%s/g, () => args[argIndex++]);
160 | // if (typeof console !== 'undefined') {
161 | // console.error(message);
162 | // }
163 | // try {
164 | // // --- Welcome to debugging React ---
165 | // // This error was thrown as a convenience so that you can use this stack
166 | // // to find the callsite that caused this warning to fire.
167 | // throw new Error(message);
168 | // } catch (x) {}
169 | // }
170 | //
171 | // warning = function(condition, format, ...args) {
172 | // if (format === undefined) {
173 | // throw new Error(
174 | // '`warning(condition, format, ...args)` requires a warning ' +
175 | // 'message argument'
176 | // );
177 | // }
178 | // if (!condition) {
179 | // printWarning(format, ...args);
180 | // }
181 | // };
182 | // }
183 |
--------------------------------------------------------------------------------
/lib/utils/index.js:
--------------------------------------------------------------------------------
1 | import { createMarkupForStyles } from './CSSPropertyOperations';
2 |
3 | export function sortObject(obj) {
4 | return Object.keys(obj)
5 | .sort()
6 | .reduce((acc, key) => {
7 | const val = obj[key];
8 | if (val || val === 0) acc[key] = val;
9 | return acc;
10 | }, {});
11 | }
12 |
13 | export function createHash(str) {
14 | let i = str.length;
15 | if (i === 0) return 0;
16 |
17 | let hash = 5381;
18 | while (i) hash = (hash * 33) ^ str.charCodeAt(--i);
19 |
20 | return hash >>> 0;
21 | }
22 |
23 | export function stringifyObject(obj) {
24 | const keys = Object.keys(obj);
25 | let str = '';
26 |
27 | for (let i = 0, len = keys.length; i < len; i++) {
28 | str += keys[i] + obj[keys[i]];
29 | }
30 |
31 | return str;
32 | }
33 |
34 | const SYMBOL_SET = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(
35 | ''
36 | );
37 | export function extendedToString(num, base) {
38 | let conversion = '';
39 |
40 | if (base > SYMBOL_SET.length || base <= 1 || !Number.isInteger(base))
41 | throw new Error(
42 | `${base} should be an integer between 1 and ${SYMBOL_SET.length}`
43 | );
44 |
45 | while (num >= 1) {
46 | conversion = SYMBOL_SET[num - base * Math.floor(num / base)] + conversion;
47 | num = Math.floor(num / base);
48 | }
49 |
50 | return base < 11 ? parseInt(conversion) : conversion;
51 | }
52 |
53 | export function createClassName(obj) {
54 | const hash = extendedToString(createHash(stringifyObject(obj)), 62);
55 | return hash ? '_' + hash : undefined;
56 | }
57 |
58 | export function createMarkup(obj) {
59 | return createMarkupForStyles(obj);
60 | }
61 |
62 | export function isEmpty(obj) {
63 | return !Object.keys(obj).length;
64 | }
65 |
66 | export function isPseudo({ style, rule }) {
67 | return rule.charAt(0) === ':' && typeof style === 'object';
68 | }
69 |
70 | export function isMediaQuery({ style, rule }) {
71 | return rule.charAt(0) === '@' && typeof style === 'object';
72 | }
73 |
74 | function handle(type, acc, { style, rule }, pseudos = []) {
75 | const hash = createClassName(sortObject(style));
76 | const rules = pseudos.length ? [[].concat(rule, style, pseudos)] : rule;
77 |
78 | acc[type] = acc[type].concat(rules);
79 | acc.style[rule] = hash;
80 | return acc;
81 | }
82 |
83 | export function seperateStyles(styles) {
84 | return Object.keys(styles).reduce(
85 | (acc, rule) => {
86 | const content = {
87 | style: styles[rule],
88 | rule
89 | };
90 |
91 | if (isPseudo(content)) {
92 | return handle('pseudos', acc, content);
93 | }
94 |
95 | if (isMediaQuery(content)) {
96 | const { style, pseudos } = seperateStyles(content.style);
97 | return handle('mediaQueries', acc, { rule, style }, pseudos);
98 | }
99 |
100 | acc.style[rule] = content.style;
101 | return acc;
102 | },
103 | {
104 | style: {},
105 | pseudos: [],
106 | mediaQueries: []
107 | }
108 | );
109 | }
110 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "stilr",
3 | "version": "2.0.0-1",
4 | "description": "Encapsulated styling for your javascript components with all the power of javascript and CSS combined. Usefull with React",
5 | "author": "Kodyl ApS",
6 | "contributors": [
7 | {
8 | "name": "Chris Kjær Sørensen"
9 | },
10 | {
11 | "name": "Daniel Juhl"
12 | }
13 | ],
14 | "repository": "kodyl/stilr",
15 | "license": "MIT",
16 | "main": "dist",
17 | "scripts": {
18 | "test": "make test",
19 | "clean": "rm -rf ./dist",
20 | "dist": "NODE_ENV=production babel lib --out-dir dist --copy-files",
21 | "release": "np"
22 | },
23 | "keywords": [
24 | "sleek",
25 | "stil",
26 | "still",
27 | "stilet",
28 | "style",
29 | "simple",
30 | "stilr",
31 | "styling",
32 | "css",
33 | "react",
34 | "inline",
35 | "component",
36 | "encapsulation",
37 | "react-component"
38 | ],
39 | "dependencies": {
40 | "@babel/runtime": "^7.0.0"
41 | },
42 | "devDependencies": {
43 | "@babel/cli": "^7.0.0",
44 | "@babel/core": "^7.0.0",
45 | "@babel/plugin-transform-runtime": "^7.0.0",
46 | "@babel/polyfill": "^7.0.0",
47 | "@babel/preset-env": "^7.0.0",
48 | "@babel/register": "^7.0.0",
49 | "babel-loader": "^8.0.0",
50 | "css": "^2.2.0",
51 | "eslint": "^3.10.2",
52 | "mocha": "^3.1.2",
53 | "mocha-clean": "^1.0.0",
54 | "np": "^5.2.1",
55 | "semver": "^5.3.0"
56 | },
57 | "prettier": {
58 | "bracketSpacing": true,
59 | "semi": true,
60 | "singleQuote": true,
61 | "trailingComma": "none"
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/stilr.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'stilr' {
2 | namespace Stilr {
3 | type StyleSheet = Map;
4 |
5 | function create(styles: any, stylesheet?: StyleSheet): any;
6 | function render(options?: Object, stylesheet?: StyleSheet): string;
7 | function clear(stylesheet: StyleSheet): Boolean;
8 | }
9 |
10 | export = Stilr;
11 | }
12 |
--------------------------------------------------------------------------------