You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

644 lines
21 KiB

4 years ago
  1. var InvalidPropertyError = require('./invalid-property-error');
  2. var wrapSingle = require('../wrap-for-optimizing').single;
  3. var Token = require('../../tokenizer/token');
  4. var Marker = require('../../tokenizer/marker');
  5. var formatPosition = require('../../utils/format-position');
  6. function _anyIsInherit(values) {
  7. var i, l;
  8. for (i = 0, l = values.length; i < l; i++) {
  9. if (values[i][1] == 'inherit') {
  10. return true;
  11. }
  12. }
  13. return false;
  14. }
  15. function _colorFilter(validator) {
  16. return function (value) {
  17. return value[1] == 'invert' || validator.isColor(value[1]) || validator.isPrefixed(value[1]);
  18. };
  19. }
  20. function _styleFilter(validator) {
  21. return function (value) {
  22. return value[1] != 'inherit' && validator.isStyleKeyword(value[1]) && !validator.isColorFunction(value[1]);
  23. };
  24. }
  25. function _wrapDefault(name, property, compactable) {
  26. var descriptor = compactable[name];
  27. if (descriptor.doubleValues && descriptor.defaultValue.length == 2) {
  28. return wrapSingle([
  29. Token.PROPERTY,
  30. [Token.PROPERTY_NAME, name],
  31. [Token.PROPERTY_VALUE, descriptor.defaultValue[0]],
  32. [Token.PROPERTY_VALUE, descriptor.defaultValue[1]]
  33. ]);
  34. } else if (descriptor.doubleValues && descriptor.defaultValue.length == 1) {
  35. return wrapSingle([
  36. Token.PROPERTY,
  37. [Token.PROPERTY_NAME, name],
  38. [Token.PROPERTY_VALUE, descriptor.defaultValue[0]]
  39. ]);
  40. } else {
  41. return wrapSingle([
  42. Token.PROPERTY,
  43. [Token.PROPERTY_NAME, name],
  44. [Token.PROPERTY_VALUE, descriptor.defaultValue]
  45. ]);
  46. }
  47. }
  48. function _widthFilter(validator) {
  49. return function (value) {
  50. return value[1] != 'inherit' &&
  51. (validator.isWidth(value[1]) || validator.isUnit(value[1]) && !validator.isDynamicUnit(value[1])) &&
  52. !validator.isStyleKeyword(value[1]) &&
  53. !validator.isColorFunction(value[1]);
  54. };
  55. }
  56. function animation(property, compactable, validator) {
  57. var duration = _wrapDefault(property.name + '-duration', property, compactable);
  58. var timing = _wrapDefault(property.name + '-timing-function', property, compactable);
  59. var delay = _wrapDefault(property.name + '-delay', property, compactable);
  60. var iteration = _wrapDefault(property.name + '-iteration-count', property, compactable);
  61. var direction = _wrapDefault(property.name + '-direction', property, compactable);
  62. var fill = _wrapDefault(property.name + '-fill-mode', property, compactable);
  63. var play = _wrapDefault(property.name + '-play-state', property, compactable);
  64. var name = _wrapDefault(property.name + '-name', property, compactable);
  65. var components = [duration, timing, delay, iteration, direction, fill, play, name];
  66. var values = property.value;
  67. var value;
  68. var durationSet = false;
  69. var timingSet = false;
  70. var delaySet = false;
  71. var iterationSet = false;
  72. var directionSet = false;
  73. var fillSet = false;
  74. var playSet = false;
  75. var nameSet = false;
  76. var i;
  77. var l;
  78. if (property.value.length == 1 && property.value[0][1] == 'inherit') {
  79. duration.value = timing.value = delay.value = iteration.value = direction.value = fill.value = play.value = name.value = property.value;
  80. return components;
  81. }
  82. if (values.length > 1 && _anyIsInherit(values)) {
  83. throw new InvalidPropertyError('Invalid animation values at ' + formatPosition(values[0][2][0]) + '. Ignoring.');
  84. }
  85. for (i = 0, l = values.length; i < l; i++) {
  86. value = values[i];
  87. if (validator.isTime(value[1]) && !durationSet) {
  88. duration.value = [value];
  89. durationSet = true;
  90. } else if (validator.isTime(value[1]) && !delaySet) {
  91. delay.value = [value];
  92. delaySet = true;
  93. } else if ((validator.isGlobal(value[1]) || validator.isTimingFunction(value[1])) && !timingSet) {
  94. timing.value = [value];
  95. timingSet = true;
  96. } else if ((validator.isAnimationIterationCountKeyword(value[1]) || validator.isPositiveNumber(value[1])) && !iterationSet) {
  97. iteration.value = [value];
  98. iterationSet = true;
  99. } else if (validator.isAnimationDirectionKeyword(value[1]) && !directionSet) {
  100. direction.value = [value];
  101. directionSet = true;
  102. } else if (validator.isAnimationFillModeKeyword(value[1]) && !fillSet) {
  103. fill.value = [value];
  104. fillSet = true;
  105. } else if (validator.isAnimationPlayStateKeyword(value[1]) && !playSet) {
  106. play.value = [value];
  107. playSet = true;
  108. } else if ((validator.isAnimationNameKeyword(value[1]) || validator.isIdentifier(value[1])) && !nameSet) {
  109. name.value = [value];
  110. nameSet = true;
  111. } else {
  112. throw new InvalidPropertyError('Invalid animation value at ' + formatPosition(value[2][0]) + '. Ignoring.');
  113. }
  114. }
  115. return components;
  116. }
  117. function background(property, compactable, validator) {
  118. var image = _wrapDefault('background-image', property, compactable);
  119. var position = _wrapDefault('background-position', property, compactable);
  120. var size = _wrapDefault('background-size', property, compactable);
  121. var repeat = _wrapDefault('background-repeat', property, compactable);
  122. var attachment = _wrapDefault('background-attachment', property, compactable);
  123. var origin = _wrapDefault('background-origin', property, compactable);
  124. var clip = _wrapDefault('background-clip', property, compactable);
  125. var color = _wrapDefault('background-color', property, compactable);
  126. var components = [image, position, size, repeat, attachment, origin, clip, color];
  127. var values = property.value;
  128. var positionSet = false;
  129. var clipSet = false;
  130. var originSet = false;
  131. var repeatSet = false;
  132. var anyValueSet = false;
  133. if (property.value.length == 1 && property.value[0][1] == 'inherit') {
  134. // NOTE: 'inherit' is not a valid value for background-attachment
  135. color.value = image.value = repeat.value = position.value = size.value = origin.value = clip.value = property.value;
  136. return components;
  137. }
  138. if (property.value.length == 1 && property.value[0][1] == '0 0') {
  139. return components;
  140. }
  141. for (var i = values.length - 1; i >= 0; i--) {
  142. var value = values[i];
  143. if (validator.isBackgroundAttachmentKeyword(value[1])) {
  144. attachment.value = [value];
  145. anyValueSet = true;
  146. } else if (validator.isBackgroundClipKeyword(value[1]) || validator.isBackgroundOriginKeyword(value[1])) {
  147. if (clipSet) {
  148. origin.value = [value];
  149. originSet = true;
  150. } else {
  151. clip.value = [value];
  152. clipSet = true;
  153. }
  154. anyValueSet = true;
  155. } else if (validator.isBackgroundRepeatKeyword(value[1])) {
  156. if (repeatSet) {
  157. repeat.value.unshift(value);
  158. } else {
  159. repeat.value = [value];
  160. repeatSet = true;
  161. }
  162. anyValueSet = true;
  163. } else if (validator.isBackgroundPositionKeyword(value[1]) || validator.isBackgroundSizeKeyword(value[1]) || validator.isUnit(value[1]) || validator.isDynamicUnit(value[1])) {
  164. if (i > 0) {
  165. var previousValue = values[i - 1];
  166. if (previousValue[1] == Marker.FORWARD_SLASH) {
  167. size.value = [value];
  168. } else if (i > 1 && values[i - 2][1] == Marker.FORWARD_SLASH) {
  169. size.value = [previousValue, value];
  170. i -= 2;
  171. } else {
  172. if (!positionSet)
  173. position.value = [];
  174. position.value.unshift(value);
  175. positionSet = true;
  176. }
  177. } else {
  178. if (!positionSet)
  179. position.value = [];
  180. position.value.unshift(value);
  181. positionSet = true;
  182. }
  183. anyValueSet = true;
  184. } else if ((color.value[0][1] == compactable[color.name].defaultValue || color.value[0][1] == 'none') && (validator.isColor(value[1]) || validator.isPrefixed(value[1]))) {
  185. color.value = [value];
  186. anyValueSet = true;
  187. } else if (validator.isUrl(value[1]) || validator.isFunction(value[1])) {
  188. image.value = [value];
  189. anyValueSet = true;
  190. }
  191. }
  192. if (clipSet && !originSet)
  193. origin.value = clip.value.slice(0);
  194. if (!anyValueSet) {
  195. throw new InvalidPropertyError('Invalid background value at ' + formatPosition(values[0][2][0]) + '. Ignoring.');
  196. }
  197. return components;
  198. }
  199. function borderRadius(property, compactable) {
  200. var values = property.value;
  201. var splitAt = -1;
  202. for (var i = 0, l = values.length; i < l; i++) {
  203. if (values[i][1] == Marker.FORWARD_SLASH) {
  204. splitAt = i;
  205. break;
  206. }
  207. }
  208. if (splitAt === 0 || splitAt === values.length - 1) {
  209. throw new InvalidPropertyError('Invalid border-radius value at ' + formatPosition(values[0][2][0]) + '. Ignoring.');
  210. }
  211. var target = _wrapDefault(property.name, property, compactable);
  212. target.value = splitAt > -1 ?
  213. values.slice(0, splitAt) :
  214. values.slice(0);
  215. target.components = fourValues(target, compactable);
  216. var remainder = _wrapDefault(property.name, property, compactable);
  217. remainder.value = splitAt > -1 ?
  218. values.slice(splitAt + 1) :
  219. values.slice(0);
  220. remainder.components = fourValues(remainder, compactable);
  221. for (var j = 0; j < 4; j++) {
  222. target.components[j].multiplex = true;
  223. target.components[j].value = target.components[j].value.concat(remainder.components[j].value);
  224. }
  225. return target.components;
  226. }
  227. function font(property, compactable, validator) {
  228. var style = _wrapDefault('font-style', property, compactable);
  229. var variant = _wrapDefault('font-variant', property, compactable);
  230. var weight = _wrapDefault('font-weight', property, compactable);
  231. var stretch = _wrapDefault('font-stretch', property, compactable);
  232. var size = _wrapDefault('font-size', property, compactable);
  233. var height = _wrapDefault('line-height', property, compactable);
  234. var family = _wrapDefault('font-family', property, compactable);
  235. var components = [style, variant, weight, stretch, size, height, family];
  236. var values = property.value;
  237. var fuzzyMatched = 4; // style, variant, weight, and stretch
  238. var index = 0;
  239. var isStretchSet = false;
  240. var isStretchValid;
  241. var isStyleSet = false;
  242. var isStyleValid;
  243. var isVariantSet = false;
  244. var isVariantValid;
  245. var isWeightSet = false;
  246. var isWeightValid;
  247. var isSizeSet = false;
  248. var appendableFamilyName = false;
  249. if (!values[index]) {
  250. throw new InvalidPropertyError('Missing font values at ' + formatPosition(property.all[property.position][1][2][0]) + '. Ignoring.');
  251. }
  252. if (values.length == 1 && values[0][1] == 'inherit') {
  253. style.value = variant.value = weight.value = stretch.value = size.value = height.value = family.value = values;
  254. return components;
  255. }
  256. if (values.length == 1 && (validator.isFontKeyword(values[0][1]) || validator.isGlobal(values[0][1]) || validator.isPrefixed(values[0][1]))) {
  257. values[0][1] = Marker.INTERNAL + values[0][1];
  258. style.value = variant.value = weight.value = stretch.value = size.value = height.value = family.value = values;
  259. return components;
  260. }
  261. if (values.length < 2 || !_anyIsFontSize(values, validator) || !_anyIsFontFamily(values, validator)) {
  262. throw new InvalidPropertyError('Invalid font values at ' + formatPosition(property.all[property.position][1][2][0]) + '. Ignoring.');
  263. }
  264. if (values.length > 1 && _anyIsInherit(values)) {
  265. throw new InvalidPropertyError('Invalid font values at ' + formatPosition(values[0][2][0]) + '. Ignoring.');
  266. }
  267. // fuzzy match style, variant, weight, and stretch on first elements
  268. while (index < fuzzyMatched) {
  269. isStretchValid = validator.isFontStretchKeyword(values[index][1]) || validator.isGlobal(values[index][1]);
  270. isStyleValid = validator.isFontStyleKeyword(values[index][1]) || validator.isGlobal(values[index][1]);
  271. isVariantValid = validator.isFontVariantKeyword(values[index][1]) || validator.isGlobal(values[index][1]);
  272. isWeightValid = validator.isFontWeightKeyword(values[index][1]) || validator.isGlobal(values[index][1]);
  273. if (isStyleValid && !isStyleSet) {
  274. style.value = [values[index]];
  275. isStyleSet = true;
  276. } else if (isVariantValid && !isVariantSet) {
  277. variant.value = [values[index]];
  278. isVariantSet = true;
  279. } else if (isWeightValid && !isWeightSet) {
  280. weight.value = [values[index]];
  281. isWeightSet = true;
  282. } else if (isStretchValid && !isStretchSet) {
  283. stretch.value = [values[index]];
  284. isStretchSet = true;
  285. } else if (isStyleValid && isStyleSet || isVariantValid && isVariantSet || isWeightValid && isWeightSet || isStretchValid && isStretchSet) {
  286. throw new InvalidPropertyError('Invalid font style / variant / weight / stretch value at ' + formatPosition(values[0][2][0]) + '. Ignoring.');
  287. } else {
  288. break;
  289. }
  290. index++;
  291. }
  292. // now comes font-size ...
  293. if (validator.isFontSizeKeyword(values[index][1]) || validator.isUnit(values[index][1]) && !validator.isDynamicUnit(values[index][1])) {
  294. size.value = [values[index]];
  295. isSizeSet = true;
  296. index++;
  297. } else {
  298. throw new InvalidPropertyError('Missing font size at ' + formatPosition(values[0][2][0]) + '. Ignoring.');
  299. }
  300. if (!values[index]) {
  301. throw new InvalidPropertyError('Missing font family at ' + formatPosition(values[0][2][0]) + '. Ignoring.');
  302. }
  303. // ... and perhaps line-height
  304. if (isSizeSet && values[index] && values[index][1] == Marker.FORWARD_SLASH && values[index + 1] && (validator.isLineHeightKeyword(values[index + 1][1]) || validator.isUnit(values[index + 1][1]) || validator.isNumber(values[index + 1][1]))) {
  305. height.value = [values[index + 1]];
  306. index++;
  307. index++;
  308. }
  309. // ... and whatever comes next is font-family
  310. family.value = [];
  311. while (values[index]) {
  312. if (values[index][1] == Marker.COMMA) {
  313. appendableFamilyName = false;
  314. } else {
  315. if (appendableFamilyName) {
  316. family.value[family.value.length - 1][1] += Marker.SPACE + values[index][1];
  317. } else {
  318. family.value.push(values[index]);
  319. }
  320. appendableFamilyName = true;
  321. }
  322. index++;
  323. }
  324. if (family.value.length === 0) {
  325. throw new InvalidPropertyError('Missing font family at ' + formatPosition(values[0][2][0]) + '. Ignoring.');
  326. }
  327. return components;
  328. }
  329. function _anyIsFontSize(values, validator) {
  330. var value;
  331. var i, l;
  332. for (i = 0, l = values.length; i < l; i++) {
  333. value = values[i];
  334. if (validator.isFontSizeKeyword(value[1]) || validator.isUnit(value[1]) && !validator.isDynamicUnit(value[1]) || validator.isFunction(value[1])) {
  335. return true;
  336. }
  337. }
  338. return false;
  339. }
  340. function _anyIsFontFamily(values, validator) {
  341. var value;
  342. var i, l;
  343. for (i = 0, l = values.length; i < l; i++) {
  344. value = values[i];
  345. if (validator.isIdentifier(value[1])) {
  346. return true;
  347. }
  348. }
  349. return false;
  350. }
  351. function fourValues(property, compactable) {
  352. var componentNames = compactable[property.name].components;
  353. var components = [];
  354. var value = property.value;
  355. if (value.length < 1)
  356. return [];
  357. if (value.length < 2)
  358. value[1] = value[0].slice(0);
  359. if (value.length < 3)
  360. value[2] = value[0].slice(0);
  361. if (value.length < 4)
  362. value[3] = value[1].slice(0);
  363. for (var i = componentNames.length - 1; i >= 0; i--) {
  364. var component = wrapSingle([
  365. Token.PROPERTY,
  366. [Token.PROPERTY_NAME, componentNames[i]]
  367. ]);
  368. component.value = [value[i]];
  369. components.unshift(component);
  370. }
  371. return components;
  372. }
  373. function multiplex(splitWith) {
  374. return function (property, compactable, validator) {
  375. var splitsAt = [];
  376. var values = property.value;
  377. var i, j, l, m;
  378. // find split commas
  379. for (i = 0, l = values.length; i < l; i++) {
  380. if (values[i][1] == ',')
  381. splitsAt.push(i);
  382. }
  383. if (splitsAt.length === 0)
  384. return splitWith(property, compactable, validator);
  385. var splitComponents = [];
  386. // split over commas, and into components
  387. for (i = 0, l = splitsAt.length; i <= l; i++) {
  388. var from = i === 0 ? 0 : splitsAt[i - 1] + 1;
  389. var to = i < l ? splitsAt[i] : values.length;
  390. var _property = _wrapDefault(property.name, property, compactable);
  391. _property.value = values.slice(from, to);
  392. splitComponents.push(splitWith(_property, compactable, validator));
  393. }
  394. var components = splitComponents[0];
  395. // group component values from each split
  396. for (i = 0, l = components.length; i < l; i++) {
  397. components[i].multiplex = true;
  398. for (j = 1, m = splitComponents.length; j < m; j++) {
  399. components[i].value.push([Token.PROPERTY_VALUE, Marker.COMMA]);
  400. Array.prototype.push.apply(components[i].value, splitComponents[j][i].value);
  401. }
  402. }
  403. return components;
  404. };
  405. }
  406. function listStyle(property, compactable, validator) {
  407. var type = _wrapDefault('list-style-type', property, compactable);
  408. var position = _wrapDefault('list-style-position', property, compactable);
  409. var image = _wrapDefault('list-style-image', property, compactable);
  410. var components = [type, position, image];
  411. if (property.value.length == 1 && property.value[0][1] == 'inherit') {
  412. type.value = position.value = image.value = [property.value[0]];
  413. return components;
  414. }
  415. var values = property.value.slice(0);
  416. var total = values.length;
  417. var index = 0;
  418. // `image` first...
  419. for (index = 0, total = values.length; index < total; index++) {
  420. if (validator.isUrl(values[index][1]) || values[index][1] == '0') {
  421. image.value = [values[index]];
  422. values.splice(index, 1);
  423. break;
  424. }
  425. }
  426. // ... then `position`
  427. for (index = 0, total = values.length; index < total; index++) {
  428. if (validator.isListStylePositionKeyword(values[index][1])) {
  429. position.value = [values[index]];
  430. values.splice(index, 1);
  431. break;
  432. }
  433. }
  434. // ... and what's left is a `type`
  435. if (values.length > 0 && (validator.isListStyleTypeKeyword(values[0][1]) || validator.isIdentifier(values[0][1]))) {
  436. type.value = [values[0]];
  437. }
  438. return components;
  439. }
  440. function transition(property, compactable, validator) {
  441. var prop = _wrapDefault(property.name + '-property', property, compactable);
  442. var duration = _wrapDefault(property.name + '-duration', property, compactable);
  443. var timing = _wrapDefault(property.name + '-timing-function', property, compactable);
  444. var delay = _wrapDefault(property.name + '-delay', property, compactable);
  445. var components = [prop, duration, timing, delay];
  446. var values = property.value;
  447. var value;
  448. var durationSet = false;
  449. var delaySet = false;
  450. var propSet = false;
  451. var timingSet = false;
  452. var i;
  453. var l;
  454. if (property.value.length == 1 && property.value[0][1] == 'inherit') {
  455. prop.value = duration.value = timing.value = delay.value = property.value;
  456. return components;
  457. }
  458. if (values.length > 1 && _anyIsInherit(values)) {
  459. throw new InvalidPropertyError('Invalid animation values at ' + formatPosition(values[0][2][0]) + '. Ignoring.');
  460. }
  461. for (i = 0, l = values.length; i < l; i++) {
  462. value = values[i];
  463. if (validator.isTime(value[1]) && !durationSet) {
  464. duration.value = [value];
  465. durationSet = true;
  466. } else if (validator.isTime(value[1]) && !delaySet) {
  467. delay.value = [value];
  468. delaySet = true;
  469. } else if ((validator.isGlobal(value[1]) || validator.isTimingFunction(value[1])) && !timingSet) {
  470. timing.value = [value];
  471. timingSet = true;
  472. } else if (validator.isIdentifier(value[1]) && !propSet) {
  473. prop.value = [value];
  474. propSet = true;
  475. } else {
  476. throw new InvalidPropertyError('Invalid animation value at ' + formatPosition(value[2][0]) + '. Ignoring.');
  477. }
  478. }
  479. return components;
  480. }
  481. function widthStyleColor(property, compactable, validator) {
  482. var descriptor = compactable[property.name];
  483. var components = [
  484. _wrapDefault(descriptor.components[0], property, compactable),
  485. _wrapDefault(descriptor.components[1], property, compactable),
  486. _wrapDefault(descriptor.components[2], property, compactable)
  487. ];
  488. var color, style, width;
  489. for (var i = 0; i < 3; i++) {
  490. var component = components[i];
  491. if (component.name.indexOf('color') > 0)
  492. color = component;
  493. else if (component.name.indexOf('style') > 0)
  494. style = component;
  495. else
  496. width = component;
  497. }
  498. if ((property.value.length == 1 && property.value[0][1] == 'inherit') ||
  499. (property.value.length == 3 && property.value[0][1] == 'inherit' && property.value[1][1] == 'inherit' && property.value[2][1] == 'inherit')) {
  500. color.value = style.value = width.value = [property.value[0]];
  501. return components;
  502. }
  503. var values = property.value.slice(0);
  504. var match, matches;
  505. // NOTE: usually users don't follow the required order of parts in this shorthand,
  506. // so we'll try to parse it caring as little about order as possible
  507. if (values.length > 0) {
  508. matches = values.filter(_widthFilter(validator));
  509. match = matches.length > 1 && (matches[0][1] == 'none' || matches[0][1] == 'auto') ? matches[1] : matches[0];
  510. if (match) {
  511. width.value = [match];
  512. values.splice(values.indexOf(match), 1);
  513. }
  514. }
  515. if (values.length > 0) {
  516. match = values.filter(_styleFilter(validator))[0];
  517. if (match) {
  518. style.value = [match];
  519. values.splice(values.indexOf(match), 1);
  520. }
  521. }
  522. if (values.length > 0) {
  523. match = values.filter(_colorFilter(validator))[0];
  524. if (match) {
  525. color.value = [match];
  526. values.splice(values.indexOf(match), 1);
  527. }
  528. }
  529. return components;
  530. }
  531. module.exports = {
  532. animation: animation,
  533. background: background,
  534. border: widthStyleColor,
  535. borderRadius: borderRadius,
  536. font: font,
  537. fourValues: fourValues,
  538. listStyle: listStyle,
  539. multiplex: multiplex,
  540. outline: widthStyleColor,
  541. transition: transition
  542. };