@compiled/react
Version:
A familiar and performant compile time CSS-in-JS library for React.
215 lines • 7.39 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.insertNonAtomicRule = exports.getStyleBucketName = exports.styleBucketOrdering = void 0;
const cache_js_1 = require("./cache.js");
const shorthand_js_1 = require("./shorthand.js");
/**
* Ordered style buckets using their short pseudo name.
*
* This is very bare-bones, with no support for nesting, like styles in
* `@media` queries, pseudo-selectors mixed with shorthand properties, etc.
*
* If changes are needed to the pseudo-selectors, make sure that it aligns with the
* definition in `packages/css/src/utils/style-ordering.ts`.
*/
exports.styleBucketOrdering = [
// shorthand properties
's-0',
's-1',
's-2',
's-3',
's-4',
's-5',
// catch-all
'',
// link
'l',
// visited
'v',
// focus-within
'w',
// focus
'f',
// focus-visible
'i',
// hover
'h',
// active
'a',
// at-rules
'm',
];
/**
* Holds all style buckets in memory that have been added to the head.
*/
const styleBucketsInHead = {};
/**
* Maps the long pseudo name to the short pseudo name.
* Pseudos that match here will be ordered,
* everything else will make their way to the catch all style bucket.
* We reduce the pseudo name to save bundlesize.
* Thankfully there aren't any overlaps, see: https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-classes.
*/
const pseudosMap = {
// link
k: 'l',
// visited
ited: 'v',
// focus-within
'us-within': 'w',
// focus
us: 'f',
// focus-visible
'us-visible': 'i',
// hover
er: 'h',
// active
ive: 'a',
};
/**
* Holds style buckets per custom container (e.g. a ShadowRoot), keyed by the
* container DOM node. Uses WeakMap so entries are garbage collected when the
* container is removed from the page.
*/
const styleBucketsByContainer = new WeakMap();
/**
* Lazily adds a `<style>` bucket to a container, defaulting to `the `<head>`
* when no container is provided in opts. Ensures style buckets are inserted in
* the correct order regardless of the target container.
*
* Each custom container (e.g. a ShadowRoot) gets its own independent set of
* buckets via the WeakMap — the main document singleton is unaffected.
*
* @param bucketName Bucket to insert into the container.
* @param opts StyleSheetOpts optionally including a custom container.
*/
function lazyAddStyleBucketToContainer(bucketName, opts) {
var _a;
const target = (_a = opts.container) !== null && _a !== void 0 ? _a : document.head;
let buckets;
if (opts.container) {
if (!styleBucketsByContainer.has(opts.container)) {
styleBucketsByContainer.set(opts.container, {});
}
buckets = styleBucketsByContainer.get(opts.container);
}
else {
buckets = styleBucketsInHead;
}
if (!buckets[bucketName]) {
let currentBucketIndex = exports.styleBucketOrdering.indexOf(bucketName) + 1;
let nextBucketFromCache = null;
// Find the next bucket which we will add our new style bucket before.
for (; currentBucketIndex < exports.styleBucketOrdering.length; currentBucketIndex++) {
const nextBucket = buckets[exports.styleBucketOrdering[currentBucketIndex]];
if (nextBucket) {
nextBucketFromCache = nextBucket;
break;
}
}
const tag = document.createElement('style');
opts.nonce && tag.setAttribute('nonce', opts.nonce);
tag.appendChild(document.createTextNode(''));
target.insertBefore(tag, nextBucketFromCache);
if (false) {
return tag;
}
buckets[bucketName] = tag;
}
return buckets[bucketName];
}
/**
* Gets the bucket depending on the sheet.
* This function makes assumptions as to the form of the input class name.
*
* Input:
*
* ```
* "._a1234567:hover{ color: red; }"
* ```
*
* Output:
*
* ```
* "h"
* ```
*
* @param sheet styles for which we are getting the bucket
*/
const getStyleBucketName = (sheet) => {
// We are grouping all the at-rules like @media, @supports etc under `m` bucket.
if (sheet.charCodeAt(0) === 64 /* "@" */) {
return 'm';
}
const firstBracket = sheet.indexOf('{');
/**
* We assume that classname will always be 9 character long,
* using this the 10th characters could be a pseudo declaration.
*/
if (sheet.charCodeAt(10) === 58 /* ":" */) {
// We send through a subset of the string instead of the full pseudo name.
// For example `"focus-visible"` name would instead of `"us-visible"`.
// Return a mapped pseudo else the default catch all bucket.
const mapped = pseudosMap[sheet.slice(14, firstBracket)];
if (mapped)
return mapped;
}
const property = sheet.slice(firstBracket + 1, sheet.indexOf(':', firstBracket)).trim();
const shorthandDepth = (0, shorthand_js_1.getShorthandDepth)(property);
if (typeof shorthandDepth === 'number') {
return `s-${shorthandDepth}`;
}
// Return default catch all bucket
return '';
};
exports.getStyleBucketName = getStyleBucketName;
/**
* Used to move styles to the head of the application during runtime.
* When `opts.container` is provided, styles are inserted into that container
* instead of `document.head` — useful for Shadow DOM rendering.
*
* @param css string
* @param opts StyleSheetOpts
*/
function insertRule(css, opts) {
const bucketName = (0, exports.getStyleBucketName)(css);
const style = lazyAddStyleBucketToContainer(bucketName, opts);
if (process.env.NODE_ENV === 'production') {
const sheet = style.sheet;
// Used to avoid unhandled exceptions across browsers with prefixed selectors such as -moz-placeholder.
try {
sheet.insertRule(css, sheet.cssRules.length);
}
catch (_a) { }
}
else {
style.appendChild(document.createTextNode(css));
}
}
exports.default = insertRule;
/**
* Inserts a non-atomic CSS rule (generated by `cssMapScoped`) into the catch-all style
* bucket, bypassing the shorthand bucket sorting logic in `getStyleBucketName`.
*
* Non-atomic rules contain multiple declarations per selector and must preserve their
* source order relative to each other. The shorthand bucket logic is designed for
* single-declaration atomic rules only — applying it to multi-declaration non-atomic
* rules would sort them by their first property, breaking the intended CSS cascade.
*/
function insertNonAtomicRule(css, opts) {
// Always use the catch-all '' bucket to preserve source-order cascade.
// The css string may contain multiple CSS rules concatenated (cssMapScoped variants
// with nested selectors). `insertRule` only accepts a single rule per call, so we
// use `Text.appendData` instead — it appends to an existing text node in a single
// DOM mutation (no new text node creation, no parsing limits, accepts multi-rule).
const style = lazyAddStyleBucketToContainer('', opts);
const text = style.firstChild;
if (text) {
text.appendData(css);
}
else {
style.appendChild(document.createTextNode(css));
}
}
exports.insertNonAtomicRule = insertNonAtomicRule;
//# sourceMappingURL=sheet.js.map