forgeplus-react/node_modules/jss/lib/renderers/DomRenderer.js

456 lines
12 KiB
JavaScript

'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;