1 | /* @flow */
|
2 | import stringHash from 'string-hash';
|
3 |
|
4 | /* ::
|
5 | type ObjectMap = { [id:string]: any };
|
6 | */
|
7 |
|
8 | const UPPERCASE_RE = /([A-Z])/g;
|
9 | const UPPERCASE_RE_TO_KEBAB = (match /* : string */) /* : string */ => `-${match.toLowerCase()}`;
|
10 |
|
11 | export const kebabifyStyleName = (string /* : string */) /* : string */ => {
|
12 | const result = string.replace(UPPERCASE_RE, UPPERCASE_RE_TO_KEBAB);
|
13 | if (result[0] === 'm' && result[1] === 's' && result[2] === '-') {
|
14 | return `-${result}`;
|
15 | }
|
16 | return result;
|
17 | };
|
18 |
|
19 | /**
|
20 | * CSS properties which accept numbers but are not in units of "px".
|
21 | * Taken from React's CSSProperty.js
|
22 | */
|
23 | const isUnitlessNumber = {
|
24 | animationIterationCount: true,
|
25 | borderImageOutset: true,
|
26 | borderImageSlice: true,
|
27 | borderImageWidth: true,
|
28 | boxFlex: true,
|
29 | boxFlexGroup: true,
|
30 | boxOrdinalGroup: true,
|
31 | columnCount: true,
|
32 | flex: true,
|
33 | flexGrow: true,
|
34 | flexPositive: true,
|
35 | flexShrink: true,
|
36 | flexNegative: true,
|
37 | flexOrder: true,
|
38 | gridRow: true,
|
39 | gridColumn: true,
|
40 | fontWeight: true,
|
41 | lineClamp: true,
|
42 | lineHeight: true,
|
43 | opacity: true,
|
44 | order: true,
|
45 | orphans: true,
|
46 | tabSize: true,
|
47 | widows: true,
|
48 | zIndex: true,
|
49 | zoom: true,
|
50 |
|
51 | // SVG-related properties
|
52 | fillOpacity: true,
|
53 | floodOpacity: true,
|
54 | stopOpacity: true,
|
55 | strokeDasharray: true,
|
56 | strokeDashoffset: true,
|
57 | strokeMiterlimit: true,
|
58 | strokeOpacity: true,
|
59 | strokeWidth: true,
|
60 | };
|
61 |
|
62 | /**
|
63 | * Taken from React's CSSProperty.js
|
64 | *
|
65 | * @param {string} prefix vendor-specific prefix, eg: Webkit
|
66 | * @param {string} key style name, eg: transitionDuration
|
67 | * @return {string} style name prefixed with `prefix`, properly camelCased, eg:
|
68 | * WebkitTransitionDuration
|
69 | */
|
70 | function prefixKey(prefix, key) {
|
71 | return prefix + key.charAt(0).toUpperCase() + key.substring(1);
|
72 | }
|
73 |
|
74 | /**
|
75 | * Support style names that may come passed in prefixed by adding permutations
|
76 | * of vendor prefixes.
|
77 | * Taken from React's CSSProperty.js
|
78 | */
|
79 | const prefixes = ['Webkit', 'ms', 'Moz', 'O'];
|
80 |
|
81 | // Using Object.keys here, or else the vanilla for-in loop makes IE8 go into an
|
82 | // infinite loop, because it iterates over the newly added props too.
|
83 | // Taken from React's CSSProperty.js
|
84 | Object.keys(isUnitlessNumber).forEach(function(prop) {
|
85 | prefixes.forEach(function(prefix) {
|
86 | isUnitlessNumber[prefixKey(prefix, prop)] = isUnitlessNumber[prop];
|
87 | });
|
88 | });
|
89 |
|
90 | export const stringifyValue = (
|
91 | key /* : string */,
|
92 | prop /* : any */
|
93 | ) /* : string */ => {
|
94 | if (typeof prop === "number") {
|
95 | if (isUnitlessNumber[key]) {
|
96 | return "" + prop;
|
97 | } else {
|
98 | return prop + "px";
|
99 | }
|
100 | } else {
|
101 | return '' + prop;
|
102 | }
|
103 | };
|
104 |
|
105 | export const stringifyAndImportantifyValue = (
|
106 | key /* : string */,
|
107 | prop /* : any */
|
108 | ) /* : string */ => importantify(stringifyValue(key, prop));
|
109 |
|
110 | // Turn a string into a hash string of base-36 values (using letters and numbers)
|
111 | // eslint-disable-next-line no-unused-vars
|
112 | export const hashString = (string /* : string */, key /* : ?string */) /* string */ => stringHash(string).toString(36);
|
113 |
|
114 | // Hash a javascript object using JSON.stringify. This is very fast, about 3
|
115 | // microseconds on my computer for a sample object:
|
116 | // http://jsperf.com/test-hashfnv32a-hash/5
|
117 | //
|
118 | // Note that this uses JSON.stringify to stringify the objects so in order for
|
119 | // this to produce consistent hashes browsers need to have a consistent
|
120 | // ordering of objects. Ben Alpert says that Facebook depends on this, so we
|
121 | // can probably depend on this too.
|
122 | export const hashObject = (object /* : ObjectMap */) /* : string */ => hashString(JSON.stringify(object));
|
123 |
|
124 | // Given a single style value string like the "b" from "a: b;", adds !important
|
125 | // to generate "b !important".
|
126 | const importantify = (string /* : string */) /* : string */ => (
|
127 | // Bracket string character access is very fast, and in the default case we
|
128 | // normally don't expect there to be "!important" at the end of the string
|
129 | // so we can use this simple check to take an optimized path. If there
|
130 | // happens to be a "!" in this position, we follow up with a more thorough
|
131 | // check.
|
132 | (string[string.length - 10] === '!' && string.slice(-11) === ' !important')
|
133 | ? string
|
134 | : `${string} !important`
|
135 | );
|