Spaces:
Sleeping
Sleeping
| ; | |
| Object.defineProperty(exports, "__esModule", { | |
| value: true | |
| }); | |
| function _export(target, all) { | |
| for(var name in all)Object.defineProperty(target, name, { | |
| enumerable: true, | |
| get: all[name] | |
| }); | |
| } | |
| _export(exports, { | |
| formatVariantSelector: function() { | |
| return formatVariantSelector; | |
| }, | |
| eliminateIrrelevantSelectors: function() { | |
| return eliminateIrrelevantSelectors; | |
| }, | |
| finalizeSelector: function() { | |
| return finalizeSelector; | |
| }, | |
| handleMergePseudo: function() { | |
| return handleMergePseudo; | |
| } | |
| }); | |
| const _postcssselectorparser = /*#__PURE__*/ _interop_require_default(require("postcss-selector-parser")); | |
| const _unesc = /*#__PURE__*/ _interop_require_default(require("postcss-selector-parser/dist/util/unesc")); | |
| const _escapeClassName = /*#__PURE__*/ _interop_require_default(require("../util/escapeClassName")); | |
| const _prefixSelector = /*#__PURE__*/ _interop_require_default(require("../util/prefixSelector")); | |
| const _pseudoElements = require("./pseudoElements"); | |
| const _splitAtTopLevelOnly = require("./splitAtTopLevelOnly"); | |
| function _interop_require_default(obj) { | |
| return obj && obj.__esModule ? obj : { | |
| default: obj | |
| }; | |
| } | |
| /** @typedef {import('postcss-selector-parser').Root} Root */ /** @typedef {import('postcss-selector-parser').Selector} Selector */ /** @typedef {import('postcss-selector-parser').Pseudo} Pseudo */ /** @typedef {import('postcss-selector-parser').Node} Node */ /** @typedef {{format: string, respectPrefix: boolean}[]} RawFormats */ /** @typedef {import('postcss-selector-parser').Root} ParsedFormats */ /** @typedef {RawFormats | ParsedFormats} AcceptedFormats */ let MERGE = ":merge"; | |
| function formatVariantSelector(formats, { context , candidate }) { | |
| var _context_tailwindConfig_prefix; | |
| let prefix = (_context_tailwindConfig_prefix = context === null || context === void 0 ? void 0 : context.tailwindConfig.prefix) !== null && _context_tailwindConfig_prefix !== void 0 ? _context_tailwindConfig_prefix : ""; | |
| // Parse the format selector into an AST | |
| let parsedFormats = formats.map((format)=>{ | |
| let ast = (0, _postcssselectorparser.default)().astSync(format.format); | |
| return { | |
| ...format, | |
| ast: format.respectPrefix ? (0, _prefixSelector.default)(prefix, ast) : ast | |
| }; | |
| }); | |
| // We start with the candidate selector | |
| let formatAst = _postcssselectorparser.default.root({ | |
| nodes: [ | |
| _postcssselectorparser.default.selector({ | |
| nodes: [ | |
| _postcssselectorparser.default.className({ | |
| value: (0, _escapeClassName.default)(candidate) | |
| }) | |
| ] | |
| }) | |
| ] | |
| }); | |
| // And iteratively merge each format selector into the candidate selector | |
| for (let { ast } of parsedFormats){ | |
| [formatAst, ast] = handleMergePseudo(formatAst, ast); | |
| // 2. Merge the format selector into the current selector AST | |
| ast.walkNesting((nesting)=>nesting.replaceWith(...formatAst.nodes[0].nodes)); | |
| // 3. Keep going! | |
| formatAst = ast; | |
| } | |
| return formatAst; | |
| } | |
| /** | |
| * Given any node in a selector this gets the "simple" selector it's a part of | |
| * A simple selector is just a list of nodes without any combinators | |
| * Technically :is(), :not(), :has(), etc… can have combinators but those are nested | |
| * inside the relevant node and won't be picked up so they're fine to ignore | |
| * | |
| * @param {Node} node | |
| * @returns {Node[]} | |
| **/ function simpleSelectorForNode(node) { | |
| /** @type {Node[]} */ let nodes = []; | |
| // Walk backwards until we hit a combinator node (or the start) | |
| while(node.prev() && node.prev().type !== "combinator"){ | |
| node = node.prev(); | |
| } | |
| // Now record all non-combinator nodes until we hit one (or the end) | |
| while(node && node.type !== "combinator"){ | |
| nodes.push(node); | |
| node = node.next(); | |
| } | |
| return nodes; | |
| } | |
| /** | |
| * Resorts the nodes in a selector to ensure they're in the correct order | |
| * Tags go before classes, and pseudo classes go after classes | |
| * | |
| * @param {Selector} sel | |
| * @returns {Selector} | |
| **/ function resortSelector(sel) { | |
| sel.sort((a, b)=>{ | |
| if (a.type === "tag" && b.type === "class") { | |
| return -1; | |
| } else if (a.type === "class" && b.type === "tag") { | |
| return 1; | |
| } else if (a.type === "class" && b.type === "pseudo" && b.value.startsWith("::")) { | |
| return -1; | |
| } else if (a.type === "pseudo" && a.value.startsWith("::") && b.type === "class") { | |
| return 1; | |
| } | |
| return sel.index(a) - sel.index(b); | |
| }); | |
| return sel; | |
| } | |
| function eliminateIrrelevantSelectors(sel, base) { | |
| let hasClassesMatchingCandidate = false; | |
| sel.walk((child)=>{ | |
| if (child.type === "class" && child.value === base) { | |
| hasClassesMatchingCandidate = true; | |
| return false // Stop walking | |
| ; | |
| } | |
| }); | |
| if (!hasClassesMatchingCandidate) { | |
| sel.remove(); | |
| } | |
| // We do NOT recursively eliminate sub selectors that don't have the base class | |
| // as this is NOT a safe operation. For example, if we have: | |
| // `.space-x-2 > :not([hidden]) ~ :not([hidden])` | |
| // We cannot remove the [hidden] from the :not() because it would change the | |
| // meaning of the selector. | |
| // TODO: Can we do this for :matches, :is, and :where? | |
| } | |
| function finalizeSelector(current, formats, { context , candidate , base }) { | |
| var _context_tailwindConfig; | |
| var _context_tailwindConfig_separator; | |
| let separator = (_context_tailwindConfig_separator = context === null || context === void 0 ? void 0 : (_context_tailwindConfig = context.tailwindConfig) === null || _context_tailwindConfig === void 0 ? void 0 : _context_tailwindConfig.separator) !== null && _context_tailwindConfig_separator !== void 0 ? _context_tailwindConfig_separator : ":"; | |
| // Split by the separator, but ignore the separator inside square brackets: | |
| // | |
| // E.g.: dark:lg:hover:[paint-order:markers] | |
| // ┬ ┬ ┬ ┬ | |
| // │ │ │ ╰── We will not split here | |
| // ╰──┴─────┴─────────────── We will split here | |
| // | |
| base = base !== null && base !== void 0 ? base : (0, _splitAtTopLevelOnly.splitAtTopLevelOnly)(candidate, separator).pop(); | |
| // Parse the selector into an AST | |
| let selector = (0, _postcssselectorparser.default)().astSync(current); | |
| // Normalize escaped classes, e.g.: | |
| // | |
| // The idea would be to replace the escaped `base` in the selector with the | |
| // `format`. However, in css you can escape the same selector in a few | |
| // different ways. This would result in different strings and therefore we | |
| // can't replace it properly. | |
| // | |
| // base: bg-[rgb(255,0,0)] | |
| // base in selector: bg-\\[rgb\\(255\\,0\\,0\\)\\] | |
| // escaped base: bg-\\[rgb\\(255\\2c 0\\2c 0\\)\\] | |
| // | |
| selector.walkClasses((node)=>{ | |
| if (node.raws && node.value.includes(base)) { | |
| node.raws.value = (0, _escapeClassName.default)((0, _unesc.default)(node.raws.value)); | |
| } | |
| }); | |
| // Remove extraneous selectors that do not include the base candidate | |
| selector.each((sel)=>eliminateIrrelevantSelectors(sel, base)); | |
| // If ffter eliminating irrelevant selectors, we end up with nothing | |
| // Then the whole "rule" this is associated with does not need to exist | |
| // We use `null` as a marker value for that case | |
| if (selector.length === 0) { | |
| return null; | |
| } | |
| // If there are no formats that means there were no variants added to the candidate | |
| // so we can just return the selector as-is | |
| let formatAst = Array.isArray(formats) ? formatVariantSelector(formats, { | |
| context, | |
| candidate | |
| }) : formats; | |
| if (formatAst === null) { | |
| return selector.toString(); | |
| } | |
| let simpleStart = _postcssselectorparser.default.comment({ | |
| value: "/*__simple__*/" | |
| }); | |
| let simpleEnd = _postcssselectorparser.default.comment({ | |
| value: "/*__simple__*/" | |
| }); | |
| // We can safely replace the escaped base now, since the `base` section is | |
| // now in a normalized escaped value. | |
| selector.walkClasses((node)=>{ | |
| if (node.value !== base) { | |
| return; | |
| } | |
| let parent = node.parent; | |
| let formatNodes = formatAst.nodes[0].nodes; | |
| // Perf optimization: if the parent is a single class we can just replace it and be done | |
| if (parent.nodes.length === 1) { | |
| node.replaceWith(...formatNodes); | |
| return; | |
| } | |
| let simpleSelector = simpleSelectorForNode(node); | |
| parent.insertBefore(simpleSelector[0], simpleStart); | |
| parent.insertAfter(simpleSelector[simpleSelector.length - 1], simpleEnd); | |
| for (let child of formatNodes){ | |
| parent.insertBefore(simpleSelector[0], child.clone()); | |
| } | |
| node.remove(); | |
| // Re-sort the simple selector to ensure it's in the correct order | |
| simpleSelector = simpleSelectorForNode(simpleStart); | |
| let firstNode = parent.index(simpleStart); | |
| parent.nodes.splice(firstNode, simpleSelector.length, ...resortSelector(_postcssselectorparser.default.selector({ | |
| nodes: simpleSelector | |
| })).nodes); | |
| simpleStart.remove(); | |
| simpleEnd.remove(); | |
| }); | |
| // Remove unnecessary pseudo selectors that we used as placeholders | |
| selector.walkPseudos((p)=>{ | |
| if (p.value === MERGE) { | |
| p.replaceWith(p.nodes); | |
| } | |
| }); | |
| // Move pseudo elements to the end of the selector (if necessary) | |
| selector.each((sel)=>(0, _pseudoElements.movePseudos)(sel)); | |
| return selector.toString(); | |
| } | |
| function handleMergePseudo(selector, format) { | |
| /** @type {{pseudo: Pseudo, value: string}[]} */ let merges = []; | |
| // Find all :merge() pseudo-classes in `selector` | |
| selector.walkPseudos((pseudo)=>{ | |
| if (pseudo.value === MERGE) { | |
| merges.push({ | |
| pseudo, | |
| value: pseudo.nodes[0].toString() | |
| }); | |
| } | |
| }); | |
| // Find all :merge() "attachments" in `format` and attach them to the matching selector in `selector` | |
| format.walkPseudos((pseudo)=>{ | |
| if (pseudo.value !== MERGE) { | |
| return; | |
| } | |
| let value = pseudo.nodes[0].toString(); | |
| // Does `selector` contain a :merge() pseudo-class with the same value? | |
| let existing = merges.find((merge)=>merge.value === value); | |
| // Nope so there's nothing to do | |
| if (!existing) { | |
| return; | |
| } | |
| // Everything after `:merge()` up to the next combinator is what is attached to the merged selector | |
| let attachments = []; | |
| let next = pseudo.next(); | |
| while(next && next.type !== "combinator"){ | |
| attachments.push(next); | |
| next = next.next(); | |
| } | |
| let combinator = next; | |
| existing.pseudo.parent.insertAfter(existing.pseudo, _postcssselectorparser.default.selector({ | |
| nodes: attachments.map((node)=>node.clone()) | |
| })); | |
| pseudo.remove(); | |
| attachments.forEach((node)=>node.remove()); | |
| // What about this case: | |
| // :merge(.group):focus > & | |
| // :merge(.group):hover & | |
| if (combinator && combinator.type === "combinator") { | |
| combinator.remove(); | |
| } | |
| }); | |
| return [ | |
| selector, | |
| format | |
| ]; | |
| } | |