Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 
 

456 строки
12 KiB

  1. 'use strict';
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
  6. var _warning = require('warning');
  7. var _warning2 = _interopRequireDefault(_warning);
  8. var _sheets = require('../sheets');
  9. var _sheets2 = _interopRequireDefault(_sheets);
  10. var _StyleRule = require('../rules/StyleRule');
  11. var _StyleRule2 = _interopRequireDefault(_StyleRule);
  12. var _toCssValue = require('../utils/toCssValue');
  13. var _toCssValue2 = _interopRequireDefault(_toCssValue);
  14. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
  15. function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
  16. /**
  17. * Cache the value from the first time a function is called.
  18. */
  19. var memoize = function memoize(fn) {
  20. var value = void 0;
  21. return function () {
  22. if (!value) value = fn();
  23. return value;
  24. };
  25. };
  26. /**
  27. * Get a style property value.
  28. */
  29. function getPropertyValue(cssRule, prop) {
  30. try {
  31. return cssRule.style.getPropertyValue(prop);
  32. } catch (err) {
  33. // IE may throw if property is unknown.
  34. return '';
  35. }
  36. }
  37. /**
  38. * Set a style property.
  39. */
  40. function setProperty(cssRule, prop, value) {
  41. try {
  42. var cssValue = value;
  43. if (Array.isArray(value)) {
  44. cssValue = (0, _toCssValue2['default'])(value, true);
  45. if (value[value.length - 1] === '!important') {
  46. cssRule.style.setProperty(prop, cssValue, 'important');
  47. return true;
  48. }
  49. }
  50. cssRule.style.setProperty(prop, cssValue);
  51. } catch (err) {
  52. // IE may throw if property is unknown.
  53. return false;
  54. }
  55. return true;
  56. }
  57. /**
  58. * Remove a style property.
  59. */
  60. function removeProperty(cssRule, prop) {
  61. try {
  62. cssRule.style.removeProperty(prop);
  63. } catch (err) {
  64. (0, _warning2['default'])(false, '[JSS] DOMException "%s" was thrown. Tried to remove property "%s".', err.message, prop);
  65. }
  66. }
  67. var CSSRuleTypes = {
  68. STYLE_RULE: 1,
  69. KEYFRAMES_RULE: 7
  70. /**
  71. * Get the CSS Rule key.
  72. */
  73. };var getKey = function () {
  74. var extractKey = function extractKey(cssText) {
  75. var from = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
  76. return cssText.substr(from, cssText.indexOf('{') - 1);
  77. };
  78. return function (cssRule) {
  79. if (cssRule.type === CSSRuleTypes.STYLE_RULE) return cssRule.selectorText;
  80. if (cssRule.type === CSSRuleTypes.KEYFRAMES_RULE) {
  81. var name = cssRule.name;
  82. if (name) return '@keyframes ' + name;
  83. // There is no rule.name in the following browsers:
  84. // - IE 9
  85. // - Safari 7.1.8
  86. // - Mobile Safari 9.0.0
  87. var cssText = cssRule.cssText;
  88. return '@' + extractKey(cssText, cssText.indexOf('keyframes'));
  89. }
  90. // Conditionals.
  91. return extractKey(cssRule.cssText);
  92. };
  93. }();
  94. /**
  95. * Set the selector.
  96. */
  97. function setSelector(cssRule, selectorText) {
  98. cssRule.selectorText = selectorText;
  99. // Return false if setter was not successful.
  100. // Currently works in chrome only.
  101. return cssRule.selectorText === selectorText;
  102. }
  103. /**
  104. * Gets the `head` element upon the first call and caches it.
  105. */
  106. var getHead = memoize(function () {
  107. return document.head || document.getElementsByTagName('head')[0];
  108. });
  109. /**
  110. * Gets a map of rule keys, where the property is an unescaped key and value
  111. * is a potentially escaped one.
  112. * It is used to identify CSS rules and the corresponding JSS rules. As an identifier
  113. * for CSSStyleRule we normally use `selectorText`. Though if original selector text
  114. * contains escaped code points e.g. `:not(#\\20)`, CSSOM will compile it to `:not(# )`
  115. * and so CSS rule's `selectorText` won't match JSS rule selector.
  116. *
  117. * https://www.w3.org/International/questions/qa-escapes#cssescapes
  118. */
  119. var getUnescapedKeysMap = function () {
  120. var style = void 0;
  121. var isAttached = false;
  122. return function (rules) {
  123. var map = {};
  124. // https://github.com/facebook/flow/issues/2696
  125. if (!style) style = document.createElement('style');
  126. for (var i = 0; i < rules.length; i++) {
  127. var rule = rules[i];
  128. if (!(rule instanceof _StyleRule2['default'])) continue;
  129. var selector = rule.selector;
  130. // Only unescape selector over CSSOM if it contains a back slash.
  131. if (selector && selector.indexOf('\\') !== -1) {
  132. // Lazilly attach when needed.
  133. if (!isAttached) {
  134. getHead().appendChild(style);
  135. isAttached = true;
  136. }
  137. style.textContent = selector + ' {}';
  138. var _style = style,
  139. sheet = _style.sheet;
  140. if (sheet) {
  141. var cssRules = sheet.cssRules;
  142. if (cssRules) map[cssRules[0].selectorText] = rule.key;
  143. }
  144. }
  145. }
  146. if (isAttached) {
  147. getHead().removeChild(style);
  148. isAttached = false;
  149. }
  150. return map;
  151. };
  152. }();
  153. /**
  154. * Find attached sheet with an index higher than the passed one.
  155. */
  156. function findHigherSheet(registry, options) {
  157. for (var i = 0; i < registry.length; i++) {
  158. var sheet = registry[i];
  159. if (sheet.attached && sheet.options.index > options.index && sheet.options.insertionPoint === options.insertionPoint) {
  160. return sheet;
  161. }
  162. }
  163. return null;
  164. }
  165. /**
  166. * Find attached sheet with the highest index.
  167. */
  168. function findHighestSheet(registry, options) {
  169. for (var i = registry.length - 1; i >= 0; i--) {
  170. var sheet = registry[i];
  171. if (sheet.attached && sheet.options.insertionPoint === options.insertionPoint) {
  172. return sheet;
  173. }
  174. }
  175. return null;
  176. }
  177. /**
  178. * Find a comment with "jss" inside.
  179. */
  180. function findCommentNode(text) {
  181. var head = getHead();
  182. for (var i = 0; i < head.childNodes.length; i++) {
  183. var node = head.childNodes[i];
  184. if (node.nodeType === 8 && node.nodeValue.trim() === text) {
  185. return node;
  186. }
  187. }
  188. return null;
  189. }
  190. /**
  191. * Find a node before which we can insert the sheet.
  192. */
  193. function findPrevNode(options) {
  194. var registry = _sheets2['default'].registry;
  195. if (registry.length > 0) {
  196. // Try to insert before the next higher sheet.
  197. var sheet = findHigherSheet(registry, options);
  198. if (sheet) return sheet.renderer.element;
  199. // Otherwise insert after the last attached.
  200. sheet = findHighestSheet(registry, options);
  201. if (sheet) return sheet.renderer.element.nextElementSibling;
  202. }
  203. // Try to find a comment placeholder if registry is empty.
  204. var insertionPoint = options.insertionPoint;
  205. if (insertionPoint && typeof insertionPoint === 'string') {
  206. var comment = findCommentNode(insertionPoint);
  207. if (comment) return comment.nextSibling;
  208. // If user specifies an insertion point and it can't be found in the document -
  209. // bad specificity issues may appear.
  210. (0, _warning2['default'])(insertionPoint === 'jss', '[JSS] Insertion point "%s" not found.', insertionPoint);
  211. }
  212. return null;
  213. }
  214. /**
  215. * Insert style element into the DOM.
  216. */
  217. function insertStyle(style, options) {
  218. var insertionPoint = options.insertionPoint;
  219. var prevNode = findPrevNode(options);
  220. if (prevNode) {
  221. var parentNode = prevNode.parentNode;
  222. if (parentNode) parentNode.insertBefore(style, prevNode);
  223. return;
  224. }
  225. // Works with iframes and any node types.
  226. if (insertionPoint && typeof insertionPoint.nodeType === 'number') {
  227. // https://stackoverflow.com/questions/41328728/force-casting-in-flow
  228. var insertionPointElement = insertionPoint;
  229. var _parentNode = insertionPointElement.parentNode;
  230. if (_parentNode) _parentNode.insertBefore(style, insertionPointElement.nextSibling);else (0, _warning2['default'])(false, '[JSS] Insertion point is not in the DOM.');
  231. return;
  232. }
  233. getHead().insertBefore(style, prevNode);
  234. }
  235. /**
  236. * Read jss nonce setting from the page if the user has set it.
  237. */
  238. var getNonce = memoize(function () {
  239. var node = document.querySelector('meta[property="csp-nonce"]');
  240. return node ? node.getAttribute('content') : null;
  241. });
  242. var DomRenderer = function () {
  243. function DomRenderer(sheet) {
  244. _classCallCheck(this, DomRenderer);
  245. this.getPropertyValue = getPropertyValue;
  246. this.setProperty = setProperty;
  247. this.removeProperty = removeProperty;
  248. this.setSelector = setSelector;
  249. this.getKey = getKey;
  250. this.getUnescapedKeysMap = getUnescapedKeysMap;
  251. this.hasInsertedRules = false;
  252. // There is no sheet when the renderer is used from a standalone StyleRule.
  253. if (sheet) _sheets2['default'].add(sheet);
  254. this.sheet = sheet;
  255. var _ref = this.sheet ? this.sheet.options : {},
  256. media = _ref.media,
  257. meta = _ref.meta,
  258. element = _ref.element;
  259. this.element = element || document.createElement('style');
  260. this.element.setAttribute('data-jss', '');
  261. if (media) this.element.setAttribute('media', media);
  262. if (meta) this.element.setAttribute('data-meta', meta);
  263. var nonce = getNonce();
  264. if (nonce) this.element.setAttribute('nonce', nonce);
  265. }
  266. /**
  267. * Insert style element into render tree.
  268. */
  269. // HTMLStyleElement needs fixing https://github.com/facebook/flow/issues/2696
  270. _createClass(DomRenderer, [{
  271. key: 'attach',
  272. value: function attach() {
  273. // In the case the element node is external and it is already in the DOM.
  274. if (this.element.parentNode || !this.sheet) return;
  275. // When rules are inserted using `insertRule` API, after `sheet.detach().attach()`
  276. // browsers remove those rules.
  277. // TODO figure out if its a bug and if it is known.
  278. // Workaround is to redeploy the sheet before attaching as a string.
  279. if (this.hasInsertedRules) {
  280. this.deploy();
  281. this.hasInsertedRules = false;
  282. }
  283. insertStyle(this.element, this.sheet.options);
  284. }
  285. /**
  286. * Remove style element from render tree.
  287. */
  288. }, {
  289. key: 'detach',
  290. value: function detach() {
  291. this.element.parentNode.removeChild(this.element);
  292. }
  293. /**
  294. * Inject CSS string into element.
  295. */
  296. }, {
  297. key: 'deploy',
  298. value: function deploy() {
  299. if (!this.sheet) return;
  300. this.element.textContent = '\n' + this.sheet.toString() + '\n';
  301. }
  302. /**
  303. * Insert a rule into element.
  304. */
  305. }, {
  306. key: 'insertRule',
  307. value: function insertRule(rule, index) {
  308. var sheet = this.element.sheet;
  309. var cssRules = sheet.cssRules;
  310. var str = rule.toString();
  311. if (!index) index = cssRules.length;
  312. if (!str) return false;
  313. try {
  314. sheet.insertRule(str, index);
  315. } catch (err) {
  316. (0, _warning2['default'])(false, '[JSS] Can not insert an unsupported rule \n\r%s', rule);
  317. return false;
  318. }
  319. this.hasInsertedRules = true;
  320. return cssRules[index];
  321. }
  322. /**
  323. * Delete a rule.
  324. */
  325. }, {
  326. key: 'deleteRule',
  327. value: function deleteRule(cssRule) {
  328. var sheet = this.element.sheet;
  329. var index = this.indexOf(cssRule);
  330. if (index === -1) return false;
  331. sheet.deleteRule(index);
  332. return true;
  333. }
  334. /**
  335. * Get index of a CSS Rule.
  336. */
  337. }, {
  338. key: 'indexOf',
  339. value: function indexOf(cssRule) {
  340. var cssRules = this.element.sheet.cssRules;
  341. for (var _index = 0; _index < cssRules.length; _index++) {
  342. if (cssRule === cssRules[_index]) return _index;
  343. }
  344. return -1;
  345. }
  346. /**
  347. * Generate a new CSS rule and replace the existing one.
  348. */
  349. }, {
  350. key: 'replaceRule',
  351. value: function replaceRule(cssRule, rule) {
  352. var index = this.indexOf(cssRule);
  353. var newCssRule = this.insertRule(rule, index);
  354. this.element.sheet.deleteRule(index);
  355. return newCssRule;
  356. }
  357. /**
  358. * Get all rules elements.
  359. */
  360. }, {
  361. key: 'getRules',
  362. value: function getRules() {
  363. return this.element.sheet.cssRules;
  364. }
  365. }]);
  366. return DomRenderer;
  367. }();
  368. exports['default'] = DomRenderer;