break-up.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. var InvalidPropertyError = require('./invalid-property-error');
  2. var wrapSingle = require('../wrap-for-optimizing').single;
  3. var Token = require('../../tokenizer/token');
  4. var formatPosition = require('../../utils/format-position');
  5. var MULTIPLEX_SEPARATOR = ',';
  6. function _colorFilter(validator) {
  7. return function (value) {
  8. return value[1] == 'invert' || validator.isValidColor(value[1]) || validator.isValidVendorPrefixedValue(value[1]);
  9. };
  10. }
  11. function _styleFilter(validator) {
  12. return function (value) {
  13. return value[1] != 'inherit' && validator.isValidStyle(value[1]) && !validator.isValidColorValue(value[1]);
  14. };
  15. }
  16. function _wrapDefault(name, property, compactable) {
  17. var descriptor = compactable[name];
  18. if (descriptor.doubleValues && descriptor.defaultValue.length == 2) {
  19. return wrapSingle([
  20. Token.PROPERTY,
  21. [Token.PROPERTY_NAME, name],
  22. [Token.PROPERTY_VALUE, descriptor.defaultValue[0]],
  23. [Token.PROPERTY_VALUE, descriptor.defaultValue[1]]
  24. ]);
  25. } else if (descriptor.doubleValues && descriptor.defaultValue.length == 1) {
  26. return wrapSingle([
  27. Token.PROPERTY,
  28. [Token.PROPERTY_NAME, name],
  29. [Token.PROPERTY_VALUE, descriptor.defaultValue[0]]
  30. ]);
  31. } else {
  32. return wrapSingle([
  33. Token.PROPERTY,
  34. [Token.PROPERTY_NAME, name],
  35. [Token.PROPERTY_VALUE, descriptor.defaultValue]
  36. ]);
  37. }
  38. }
  39. function _widthFilter(validator) {
  40. return function (value) {
  41. return value[1] != 'inherit' && validator.isValidWidth(value[1]) && !validator.isValidStyle(value[1]) && !validator.isValidColorValue(value[1]);
  42. };
  43. }
  44. function background(property, compactable, validator) {
  45. var image = _wrapDefault('background-image', property, compactable);
  46. var position = _wrapDefault('background-position', property, compactable);
  47. var size = _wrapDefault('background-size', property, compactable);
  48. var repeat = _wrapDefault('background-repeat', property, compactable);
  49. var attachment = _wrapDefault('background-attachment', property, compactable);
  50. var origin = _wrapDefault('background-origin', property, compactable);
  51. var clip = _wrapDefault('background-clip', property, compactable);
  52. var color = _wrapDefault('background-color', property, compactable);
  53. var components = [image, position, size, repeat, attachment, origin, clip, color];
  54. var values = property.value;
  55. var positionSet = false;
  56. var clipSet = false;
  57. var originSet = false;
  58. var repeatSet = false;
  59. var anyValueSet = false;
  60. if (property.value.length == 1 && property.value[0][1] == 'inherit') {
  61. // NOTE: 'inherit' is not a valid value for background-attachment
  62. color.value = image.value = repeat.value = position.value = size.value = origin.value = clip.value = property.value;
  63. return components;
  64. }
  65. if (property.value.length == 1 && property.value[0][1] == '0 0') {
  66. return components;
  67. }
  68. for (var i = values.length - 1; i >= 0; i--) {
  69. var value = values[i];
  70. if (validator.isValidBackgroundAttachment(value[1])) {
  71. attachment.value = [value];
  72. anyValueSet = true;
  73. } else if (validator.isValidBackgroundClip(value[1]) || validator.isValidBackgroundOrigin(value[1])) {
  74. if (clipSet) {
  75. origin.value = [value];
  76. originSet = true;
  77. } else {
  78. clip.value = [value];
  79. clipSet = true;
  80. }
  81. anyValueSet = true;
  82. } else if (validator.isValidBackgroundRepeat(value[1])) {
  83. if (repeatSet) {
  84. repeat.value.unshift(value);
  85. } else {
  86. repeat.value = [value];
  87. repeatSet = true;
  88. }
  89. anyValueSet = true;
  90. } else if (validator.isValidBackgroundPositionPart(value[1]) || validator.isValidBackgroundSizePart(value[1])) {
  91. if (i > 0) {
  92. var previousValue = values[i - 1];
  93. if (previousValue[1] == '/') {
  94. size.value = [value];
  95. } else if (i > 1 && values[i - 2][1] == '/') {
  96. size.value = [previousValue, value];
  97. i -= 2;
  98. } else {
  99. if (!positionSet)
  100. position.value = [];
  101. position.value.unshift(value);
  102. positionSet = true;
  103. }
  104. } else {
  105. if (!positionSet)
  106. position.value = [];
  107. position.value.unshift(value);
  108. positionSet = true;
  109. }
  110. anyValueSet = true;
  111. } else if ((color.value[0][1] == compactable[color.name].defaultValue || color.value[0][1] == 'none') && (validator.isValidColor(value[1]) || validator.isValidVendorPrefixedValue(value[1]))) {
  112. color.value = [value];
  113. anyValueSet = true;
  114. } else if (validator.isValidUrl(value[1]) || validator.isValidFunction(value[1])) {
  115. image.value = [value];
  116. anyValueSet = true;
  117. }
  118. }
  119. if (clipSet && !originSet)
  120. origin.value = clip.value.slice(0);
  121. if (!anyValueSet) {
  122. throw new InvalidPropertyError('Invalid background value at ' + formatPosition(values[0][2][0]) + '. Ignoring.');
  123. }
  124. return components;
  125. }
  126. function borderRadius(property, compactable) {
  127. var values = property.value;
  128. var splitAt = -1;
  129. for (var i = 0, l = values.length; i < l; i++) {
  130. if (values[i][1] == '/') {
  131. splitAt = i;
  132. break;
  133. }
  134. }
  135. if (splitAt === 0 || splitAt === values.length - 1) {
  136. throw new InvalidPropertyError('Invalid border-radius value at ' + formatPosition(values[0][2][0]) + '. Ignoring.');
  137. }
  138. var target = _wrapDefault(property.name, property, compactable);
  139. target.value = splitAt > -1 ?
  140. values.slice(0, splitAt) :
  141. values.slice(0);
  142. target.components = fourValues(target, compactable);
  143. var remainder = _wrapDefault(property.name, property, compactable);
  144. remainder.value = splitAt > -1 ?
  145. values.slice(splitAt + 1) :
  146. values.slice(0);
  147. remainder.components = fourValues(remainder, compactable);
  148. for (var j = 0; j < 4; j++) {
  149. target.components[j].multiplex = true;
  150. target.components[j].value = target.components[j].value.concat(remainder.components[j].value);
  151. }
  152. return target.components;
  153. }
  154. function fourValues(property, compactable) {
  155. var componentNames = compactable[property.name].components;
  156. var components = [];
  157. var value = property.value;
  158. if (value.length < 1)
  159. return [];
  160. if (value.length < 2)
  161. value[1] = value[0].slice(0);
  162. if (value.length < 3)
  163. value[2] = value[0].slice(0);
  164. if (value.length < 4)
  165. value[3] = value[1].slice(0);
  166. for (var i = componentNames.length - 1; i >= 0; i--) {
  167. var component = wrapSingle([
  168. Token.PROPERTY,
  169. [Token.PROPERTY_NAME, componentNames[i]]
  170. ]);
  171. component.value = [value[i]];
  172. components.unshift(component);
  173. }
  174. return components;
  175. }
  176. function multiplex(splitWith) {
  177. return function (property, compactable, validator) {
  178. var splitsAt = [];
  179. var values = property.value;
  180. var i, j, l, m;
  181. // find split commas
  182. for (i = 0, l = values.length; i < l; i++) {
  183. if (values[i][1] == ',')
  184. splitsAt.push(i);
  185. }
  186. if (splitsAt.length === 0)
  187. return splitWith(property, compactable, validator);
  188. var splitComponents = [];
  189. // split over commas, and into components
  190. for (i = 0, l = splitsAt.length; i <= l; i++) {
  191. var from = i === 0 ? 0 : splitsAt[i - 1] + 1;
  192. var to = i < l ? splitsAt[i] : values.length;
  193. var _property = _wrapDefault(property.name, property, compactable);
  194. _property.value = values.slice(from, to);
  195. splitComponents.push(splitWith(_property, compactable, validator));
  196. }
  197. var components = splitComponents[0];
  198. // group component values from each split
  199. for (i = 0, l = components.length; i < l; i++) {
  200. components[i].multiplex = true;
  201. for (j = 1, m = splitComponents.length; j < m; j++) {
  202. components[i].value.push([Token.PROPERTY_VALUE, MULTIPLEX_SEPARATOR]);
  203. Array.prototype.push.apply(components[i].value, splitComponents[j][i].value);
  204. }
  205. }
  206. return components;
  207. };
  208. }
  209. function listStyle(property, compactable, validator) {
  210. var type = _wrapDefault('list-style-type', property, compactable);
  211. var position = _wrapDefault('list-style-position', property, compactable);
  212. var image = _wrapDefault('list-style-image', property, compactable);
  213. var components = [type, position, image];
  214. if (property.value.length == 1 && property.value[0][1] == 'inherit') {
  215. type.value = position.value = image.value = [property.value[0]];
  216. return components;
  217. }
  218. var values = property.value.slice(0);
  219. var total = values.length;
  220. var index = 0;
  221. // `image` first...
  222. for (index = 0, total = values.length; index < total; index++) {
  223. if (validator.isValidUrl(values[index][1]) || values[index][1] == '0') {
  224. image.value = [values[index]];
  225. values.splice(index, 1);
  226. break;
  227. }
  228. }
  229. // ... then `type`...
  230. for (index = 0, total = values.length; index < total; index++) {
  231. if (validator.isValidListStyleType(values[index][1])) {
  232. type.value = [values[index]];
  233. values.splice(index, 1);
  234. break;
  235. }
  236. }
  237. // ... and what's left is a `position`
  238. if (values.length > 0 && validator.isValidListStylePosition(values[0][1]))
  239. position.value = [values[0]];
  240. return components;
  241. }
  242. function widthStyleColor(property, compactable, validator) {
  243. var descriptor = compactable[property.name];
  244. var components = [
  245. _wrapDefault(descriptor.components[0], property, compactable),
  246. _wrapDefault(descriptor.components[1], property, compactable),
  247. _wrapDefault(descriptor.components[2], property, compactable)
  248. ];
  249. var color, style, width;
  250. for (var i = 0; i < 3; i++) {
  251. var component = components[i];
  252. if (component.name.indexOf('color') > 0)
  253. color = component;
  254. else if (component.name.indexOf('style') > 0)
  255. style = component;
  256. else
  257. width = component;
  258. }
  259. if ((property.value.length == 1 && property.value[0][1] == 'inherit') ||
  260. (property.value.length == 3 && property.value[0][1] == 'inherit' && property.value[1][1] == 'inherit' && property.value[2][1] == 'inherit')) {
  261. color.value = style.value = width.value = [property.value[0]];
  262. return components;
  263. }
  264. var values = property.value.slice(0);
  265. var match, matches;
  266. // NOTE: usually users don't follow the required order of parts in this shorthand,
  267. // so we'll try to parse it caring as little about order as possible
  268. if (values.length > 0) {
  269. matches = values.filter(_widthFilter(validator));
  270. match = matches.length > 1 && (matches[0][1] == 'none' || matches[0][1] == 'auto') ? matches[1] : matches[0];
  271. if (match) {
  272. width.value = [match];
  273. values.splice(values.indexOf(match), 1);
  274. }
  275. }
  276. if (values.length > 0) {
  277. match = values.filter(_styleFilter(validator))[0];
  278. if (match) {
  279. style.value = [match];
  280. values.splice(values.indexOf(match), 1);
  281. }
  282. }
  283. if (values.length > 0) {
  284. match = values.filter(_colorFilter(validator))[0];
  285. if (match) {
  286. color.value = [match];
  287. values.splice(values.indexOf(match), 1);
  288. }
  289. }
  290. return components;
  291. }
  292. module.exports = {
  293. background: background,
  294. border: widthStyleColor,
  295. borderRadius: borderRadius,
  296. fourValues: fourValues,
  297. listStyle: listStyle,
  298. multiplex: multiplex,
  299. outline: widthStyleColor
  300. };