|
- 'use strict';
-
- Object.defineProperty(exports, "__esModule", {
- value: true
- });
-
- 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; }; }();
-
- var _warning = require('warning');
-
- var _warning2 = _interopRequireDefault(_warning);
-
- var _sheets = require('../sheets');
-
- var _sheets2 = _interopRequireDefault(_sheets);
-
- var _StyleRule = require('../rules/StyleRule');
-
- var _StyleRule2 = _interopRequireDefault(_StyleRule);
-
- var _toCssValue = require('../utils/toCssValue');
-
- var _toCssValue2 = _interopRequireDefault(_toCssValue);
-
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
-
- function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
-
- /**
- * Cache the value from the first time a function is called.
- */
- var memoize = function memoize(fn) {
- var value = void 0;
- return function () {
- if (!value) value = fn();
- return value;
- };
- };
-
- /**
- * Get a style property value.
- */
- function getPropertyValue(cssRule, prop) {
- try {
- return cssRule.style.getPropertyValue(prop);
- } catch (err) {
- // IE may throw if property is unknown.
- return '';
- }
- }
-
- /**
- * Set a style property.
- */
- function setProperty(cssRule, prop, value) {
- try {
- var cssValue = value;
-
- if (Array.isArray(value)) {
- cssValue = (0, _toCssValue2['default'])(value, true);
-
- if (value[value.length - 1] === '!important') {
- cssRule.style.setProperty(prop, cssValue, 'important');
- return true;
- }
- }
-
- cssRule.style.setProperty(prop, cssValue);
- } catch (err) {
- // IE may throw if property is unknown.
- return false;
- }
- return true;
- }
-
- /**
- * Remove a style property.
- */
- function removeProperty(cssRule, prop) {
- try {
- cssRule.style.removeProperty(prop);
- } catch (err) {
- (0, _warning2['default'])(false, '[JSS] DOMException "%s" was thrown. Tried to remove property "%s".', err.message, prop);
- }
- }
-
- var CSSRuleTypes = {
- STYLE_RULE: 1,
- KEYFRAMES_RULE: 7
-
- /**
- * Get the CSS Rule key.
- */
-
- };var getKey = function () {
- var extractKey = function extractKey(cssText) {
- var from = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
- return cssText.substr(from, cssText.indexOf('{') - 1);
- };
-
- return function (cssRule) {
- if (cssRule.type === CSSRuleTypes.STYLE_RULE) return cssRule.selectorText;
- if (cssRule.type === CSSRuleTypes.KEYFRAMES_RULE) {
- var name = cssRule.name;
-
- if (name) return '@keyframes ' + name;
-
- // There is no rule.name in the following browsers:
- // - IE 9
- // - Safari 7.1.8
- // - Mobile Safari 9.0.0
- var cssText = cssRule.cssText;
-
- return '@' + extractKey(cssText, cssText.indexOf('keyframes'));
- }
-
- // Conditionals.
- return extractKey(cssRule.cssText);
- };
- }();
-
- /**
- * Set the selector.
- */
- function setSelector(cssRule, selectorText) {
- cssRule.selectorText = selectorText;
-
- // Return false if setter was not successful.
- // Currently works in chrome only.
- return cssRule.selectorText === selectorText;
- }
-
- /**
- * Gets the `head` element upon the first call and caches it.
- */
- var getHead = memoize(function () {
- return document.head || document.getElementsByTagName('head')[0];
- });
-
- /**
- * Gets a map of rule keys, where the property is an unescaped key and value
- * is a potentially escaped one.
- * It is used to identify CSS rules and the corresponding JSS rules. As an identifier
- * for CSSStyleRule we normally use `selectorText`. Though if original selector text
- * contains escaped code points e.g. `:not(#\\20)`, CSSOM will compile it to `:not(# )`
- * and so CSS rule's `selectorText` won't match JSS rule selector.
- *
- * https://www.w3.org/International/questions/qa-escapes#cssescapes
- */
- var getUnescapedKeysMap = function () {
- var style = void 0;
- var isAttached = false;
-
- return function (rules) {
- var map = {};
- // https://github.com/facebook/flow/issues/2696
- if (!style) style = document.createElement('style');
- for (var i = 0; i < rules.length; i++) {
- var rule = rules[i];
- if (!(rule instanceof _StyleRule2['default'])) continue;
- var selector = rule.selector;
- // Only unescape selector over CSSOM if it contains a back slash.
-
- if (selector && selector.indexOf('\\') !== -1) {
- // Lazilly attach when needed.
- if (!isAttached) {
- getHead().appendChild(style);
- isAttached = true;
- }
- style.textContent = selector + ' {}';
- var _style = style,
- sheet = _style.sheet;
-
- if (sheet) {
- var cssRules = sheet.cssRules;
-
- if (cssRules) map[cssRules[0].selectorText] = rule.key;
- }
- }
- }
- if (isAttached) {
- getHead().removeChild(style);
- isAttached = false;
- }
- return map;
- };
- }();
-
- /**
- * Find attached sheet with an index higher than the passed one.
- */
- function findHigherSheet(registry, options) {
- for (var i = 0; i < registry.length; i++) {
- var sheet = registry[i];
- if (sheet.attached && sheet.options.index > options.index && sheet.options.insertionPoint === options.insertionPoint) {
- return sheet;
- }
- }
- return null;
- }
-
- /**
- * Find attached sheet with the highest index.
- */
- function findHighestSheet(registry, options) {
- for (var i = registry.length - 1; i >= 0; i--) {
- var sheet = registry[i];
- if (sheet.attached && sheet.options.insertionPoint === options.insertionPoint) {
- return sheet;
- }
- }
- return null;
- }
-
- /**
- * Find a comment with "jss" inside.
- */
- function findCommentNode(text) {
- var head = getHead();
- for (var i = 0; i < head.childNodes.length; i++) {
- var node = head.childNodes[i];
- if (node.nodeType === 8 && node.nodeValue.trim() === text) {
- return node;
- }
- }
- return null;
- }
-
- /**
- * Find a node before which we can insert the sheet.
- */
- function findPrevNode(options) {
- var registry = _sheets2['default'].registry;
-
-
- if (registry.length > 0) {
- // Try to insert before the next higher sheet.
- var sheet = findHigherSheet(registry, options);
- if (sheet) return sheet.renderer.element;
-
- // Otherwise insert after the last attached.
- sheet = findHighestSheet(registry, options);
- if (sheet) return sheet.renderer.element.nextElementSibling;
- }
-
- // Try to find a comment placeholder if registry is empty.
- var insertionPoint = options.insertionPoint;
-
- if (insertionPoint && typeof insertionPoint === 'string') {
- var comment = findCommentNode(insertionPoint);
- if (comment) return comment.nextSibling;
- // If user specifies an insertion point and it can't be found in the document -
- // bad specificity issues may appear.
- (0, _warning2['default'])(insertionPoint === 'jss', '[JSS] Insertion point "%s" not found.', insertionPoint);
- }
-
- return null;
- }
-
- /**
- * Insert style element into the DOM.
- */
- function insertStyle(style, options) {
- var insertionPoint = options.insertionPoint;
-
- var prevNode = findPrevNode(options);
-
- if (prevNode) {
- var parentNode = prevNode.parentNode;
-
- if (parentNode) parentNode.insertBefore(style, prevNode);
- return;
- }
-
- // Works with iframes and any node types.
- if (insertionPoint && typeof insertionPoint.nodeType === 'number') {
- // https://stackoverflow.com/questions/41328728/force-casting-in-flow
- var insertionPointElement = insertionPoint;
- var _parentNode = insertionPointElement.parentNode;
-
- if (_parentNode) _parentNode.insertBefore(style, insertionPointElement.nextSibling);else (0, _warning2['default'])(false, '[JSS] Insertion point is not in the DOM.');
- return;
- }
-
- getHead().insertBefore(style, prevNode);
- }
-
- /**
- * Read jss nonce setting from the page if the user has set it.
- */
- var getNonce = memoize(function () {
- var node = document.querySelector('meta[property="csp-nonce"]');
- return node ? node.getAttribute('content') : null;
- });
-
- var DomRenderer = function () {
- function DomRenderer(sheet) {
- _classCallCheck(this, DomRenderer);
-
- this.getPropertyValue = getPropertyValue;
- this.setProperty = setProperty;
- this.removeProperty = removeProperty;
- this.setSelector = setSelector;
- this.getKey = getKey;
- this.getUnescapedKeysMap = getUnescapedKeysMap;
- this.hasInsertedRules = false;
-
- // There is no sheet when the renderer is used from a standalone StyleRule.
- if (sheet) _sheets2['default'].add(sheet);
-
- this.sheet = sheet;
-
- var _ref = this.sheet ? this.sheet.options : {},
- media = _ref.media,
- meta = _ref.meta,
- element = _ref.element;
-
- this.element = element || document.createElement('style');
- this.element.setAttribute('data-jss', '');
- if (media) this.element.setAttribute('media', media);
- if (meta) this.element.setAttribute('data-meta', meta);
- var nonce = getNonce();
- if (nonce) this.element.setAttribute('nonce', nonce);
- }
-
- /**
- * Insert style element into render tree.
- */
-
-
- // HTMLStyleElement needs fixing https://github.com/facebook/flow/issues/2696
-
-
- _createClass(DomRenderer, [{
- key: 'attach',
- value: function attach() {
- // In the case the element node is external and it is already in the DOM.
- if (this.element.parentNode || !this.sheet) return;
-
- // When rules are inserted using `insertRule` API, after `sheet.detach().attach()`
- // browsers remove those rules.
- // TODO figure out if its a bug and if it is known.
- // Workaround is to redeploy the sheet before attaching as a string.
- if (this.hasInsertedRules) {
- this.deploy();
- this.hasInsertedRules = false;
- }
-
- insertStyle(this.element, this.sheet.options);
- }
-
- /**
- * Remove style element from render tree.
- */
-
- }, {
- key: 'detach',
- value: function detach() {
- this.element.parentNode.removeChild(this.element);
- }
-
- /**
- * Inject CSS string into element.
- */
-
- }, {
- key: 'deploy',
- value: function deploy() {
- if (!this.sheet) return;
- this.element.textContent = '\n' + this.sheet.toString() + '\n';
- }
-
- /**
- * Insert a rule into element.
- */
-
- }, {
- key: 'insertRule',
- value: function insertRule(rule, index) {
- var sheet = this.element.sheet;
- var cssRules = sheet.cssRules;
-
- var str = rule.toString();
- if (!index) index = cssRules.length;
-
- if (!str) return false;
-
- try {
- sheet.insertRule(str, index);
- } catch (err) {
- (0, _warning2['default'])(false, '[JSS] Can not insert an unsupported rule \n\r%s', rule);
- return false;
- }
- this.hasInsertedRules = true;
-
- return cssRules[index];
- }
-
- /**
- * Delete a rule.
- */
-
- }, {
- key: 'deleteRule',
- value: function deleteRule(cssRule) {
- var sheet = this.element.sheet;
-
- var index = this.indexOf(cssRule);
- if (index === -1) return false;
- sheet.deleteRule(index);
- return true;
- }
-
- /**
- * Get index of a CSS Rule.
- */
-
- }, {
- key: 'indexOf',
- value: function indexOf(cssRule) {
- var cssRules = this.element.sheet.cssRules;
-
- for (var _index = 0; _index < cssRules.length; _index++) {
- if (cssRule === cssRules[_index]) return _index;
- }
- return -1;
- }
-
- /**
- * Generate a new CSS rule and replace the existing one.
- */
-
- }, {
- key: 'replaceRule',
- value: function replaceRule(cssRule, rule) {
- var index = this.indexOf(cssRule);
- var newCssRule = this.insertRule(rule, index);
- this.element.sheet.deleteRule(index);
- return newCssRule;
- }
-
- /**
- * Get all rules elements.
- */
-
- }, {
- key: 'getRules',
- value: function getRules() {
- return this.element.sheet.cssRules;
- }
- }]);
-
- return DomRenderer;
- }();
-
- exports['default'] = DomRenderer;
|