456 lines
12 KiB
JavaScript
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; |