UNPKG

@compiled/react

Version:

A familiar and performant compile time CSS-in-JS library for React.

209 lines 7.07 kB
import { isCacheDisabled } from './cache.js'; import { getShorthandDepth } from './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`. */ export const 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 = styleBucketOrdering.indexOf(bucketName) + 1; let nextBucketFromCache = null; // Find the next bucket which we will add our new style bucket before. for (; currentBucketIndex < styleBucketOrdering.length; currentBucketIndex++) { const nextBucket = buckets[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 */ export 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 = getShorthandDepth(property); if (typeof shorthandDepth === 'number') { return `s-${shorthandDepth}`; } // Return default catch all bucket return ''; }; /** * 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 */ export default function insertRule(css, opts) { const bucketName = 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)); } } /** * 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. */ export 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)); } } //# sourceMappingURL=sheet.js.map