@wordpress/block-editor
Version:
182 lines (172 loc) • 7.7 kB
JavaScript
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var parsel = _interopRequireWildcard(require("parsel-js"));
var _processor = _interopRequireDefault(require("postcss/lib/processor"));
var _cssSyntaxError = _interopRequireDefault(require("postcss/lib/css-syntax-error"));
var _postcssPrefixSelector = _interopRequireDefault(require("postcss-prefix-selector"));
var _postcssUrlrebase = _interopRequireDefault(require("postcss-urlrebase"));
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
/**
* External dependencies
*/
const cacheByWrapperSelector = new Map();
const ROOT_SELECTOR_TOKENS = [{
type: 'type',
content: 'body'
}, {
type: 'type',
content: 'html'
}, {
type: 'pseudo-class',
content: ':root'
}, {
type: 'pseudo-class',
content: ':where(body)'
}, {
type: 'pseudo-class',
content: ':where(:root)'
}, {
type: 'pseudo-class',
content: ':where(html)'
}];
/**
* Prefixes root selectors in a way that ensures consistent specificity.
* This requires special handling, since prefixing a classname before
* html, body, or :root will generally result in an invalid selector.
*
* Some libraries will simply replace the root selector with the prefix
* instead, but this results in inconsistent specificity.
*
* This function instead inserts the prefix after the root tags but before
* any other part of the selector. This results in consistent specificity:
* - If a `:where()` selector is used for the prefix, all selectors output
* by `transformStyles` will have no specificity increase.
* - If a classname, id, or something else is used as the prefix, all selectors
* will have the same specificity bump when transformed.
*
* @param {string} prefix The prefix.
* @param {string} selector The selector.
*
* @return {string} The prefixed root selector.
*/
function prefixRootSelector(prefix, selector) {
// Use a tokenizer, since regular expressions are unreliable.
const tokenized = parsel.tokenize(selector);
// Find the last token that contains a root selector by walking back
// through the tokens.
const lastRootIndex = tokenized.findLastIndex(({
content,
type
}) => {
return ROOT_SELECTOR_TOKENS.some(rootSelector => content === rootSelector.content && type === rootSelector.type);
});
// Walk forwards to find the combinator after the last root.
// This is where the root ends and the rest of the selector begins,
// and the index to insert before.
// Doing it this way takes into account that a root selector like
// 'body' may have additional id/class/pseudo-class/attribute-selector
// parts chained to it, which is difficult to quantify using a regex.
let insertionPoint = -1;
for (let i = lastRootIndex + 1; i < tokenized.length; i++) {
if (tokenized[i].type === 'combinator') {
insertionPoint = i;
break;
}
}
// Tokenize and insert the prefix with a ' ' combinator before it.
const tokenizedPrefix = parsel.tokenize(prefix);
tokenized.splice(
// Insert at the insertion point, or the end.
insertionPoint === -1 ? tokenized.length : insertionPoint, 0, {
type: 'combinator',
content: ' '
}, ...tokenizedPrefix);
return parsel.stringify(tokenized);
}
function transformStyle({
css,
ignoredSelectors = [],
baseURL
}, wrapperSelector = '', transformOptions) {
// When there is no wrapper selector and no base URL, there is no need
// to transform the CSS. This is most cases because in the default
// iframed editor, no wrapping is needed, and not many styles
// provide a base URL.
if (!wrapperSelector && !baseURL) {
return css;
}
try {
var _transformOptions$ign;
const excludedSelectors = [...ignoredSelectors, ...((_transformOptions$ign = transformOptions?.ignoredSelectors) !== null && _transformOptions$ign !== void 0 ? _transformOptions$ign : []), wrapperSelector];
return new _processor.default([wrapperSelector && (0, _postcssPrefixSelector.default)({
prefix: wrapperSelector,
transform(prefix, selector, prefixedSelector) {
// For backwards compatibility, don't use the `exclude` option
// of postcss-prefix-selector, instead handle it here to match
// the behavior of the old library (postcss-prefix-wrap) that
// `transformStyle` previously used.
if (excludedSelectors.some(excludedSelector => excludedSelector instanceof RegExp ? selector.match(excludedSelector) : selector.includes(excludedSelector))) {
return selector;
}
const hasRootSelector = ROOT_SELECTOR_TOKENS.some(rootSelector => selector.startsWith(rootSelector.content));
// Reorganize root selectors such that the root part comes before the prefix,
// but the prefix still comes before the remaining part of the selector.
if (hasRootSelector) {
return prefixRootSelector(prefix, selector);
}
return prefixedSelector;
}
}), baseURL && (0, _postcssUrlrebase.default)({
rootUrl: baseURL
})].filter(Boolean)).process(css, {}).css; // use sync PostCSS API
} catch (error) {
if (error instanceof _cssSyntaxError.default) {
// eslint-disable-next-line no-console
console.warn('wp.blockEditor.transformStyles Failed to transform CSS.', error.message + '\n' + error.showSourceCode(false));
} else {
// eslint-disable-next-line no-console
console.warn('wp.blockEditor.transformStyles Failed to transform CSS.', error);
}
return null;
}
}
/**
* @typedef {Object} EditorStyle
* @property {string} css the CSS block(s), as a single string.
* @property {?string} baseURL the base URL to be used as the reference when rewriting urls.
* @property {?string[]} ignoredSelectors the selectors not to wrap.
*/
/**
* @typedef {Object} TransformOptions
* @property {?string[]} ignoredSelectors the selectors not to wrap.
*/
/**
* Applies a series of CSS rule transforms to wrap selectors inside a given class and/or rewrite URLs depending on the parameters passed.
*
* @param {EditorStyle[]} styles CSS rules.
* @param {string} wrapperSelector Wrapper selector.
* @param {TransformOptions} transformOptions Additional options for style transformation.
* @return {Array} converted rules.
*/
const transformStyles = (styles, wrapperSelector = '', transformOptions) => {
let cache = cacheByWrapperSelector.get(wrapperSelector);
if (!cache) {
cache = new WeakMap();
cacheByWrapperSelector.set(wrapperSelector, cache);
}
return styles.map(style => {
let css = cache.get(style);
if (!css) {
css = transformStyle(style, wrapperSelector, transformOptions);
cache.set(style, css);
}
return css;
});
};
var _default = exports.default = transformStyles;
//# sourceMappingURL=index.js.map
;