"use strict"; 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, { selectorFunctions: ()=>selectorFunctions, formatVariantSelector: ()=>formatVariantSelector, finalizeSelector: ()=>finalizeSelector }); const _postcssSelectorParser = /*#__PURE__*/ _interopRequireDefault(require("postcss-selector-parser")); const _unesc = /*#__PURE__*/ _interopRequireDefault(require("postcss-selector-parser/dist/util/unesc")); const _escapeClassName = /*#__PURE__*/ _interopRequireDefault(require("../util/escapeClassName")); const _prefixSelector = /*#__PURE__*/ _interopRequireDefault(require("../util/prefixSelector")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } var ref; let MERGE = ":merge"; let PARENT = "&"; let selectorFunctions = new Set([ MERGE ]); function formatVariantSelector(current, ...others) { for (let other of others){ let incomingValue = resolveFunctionArgument(other, MERGE); if (incomingValue !== null) { let existingValue = resolveFunctionArgument(current, MERGE, incomingValue); if (existingValue !== null) { let existingTarget = `${MERGE}(${incomingValue})`; let splitIdx = other.indexOf(existingTarget); let addition = other.slice(splitIdx + existingTarget.length).split(" ")[0]; current = current.replace(existingTarget, existingTarget + addition); continue; } } current = other.replace(PARENT, current); } return current; } /** * 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 {import('postcss-selector-parser').Node} node * @returns {import('postcss-selector-parser').Node[]} **/ function simpleSelectorForNode(node) { /** @type {import('postcss-selector-parser').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 {import('postcss-selector-parser').Selector} sel * @returns {import('postcss-selector-parser').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 !== ":merge") { return -1; } else if (a.type === "pseudo" && a.value !== ":merge" && 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? } var ref1; function finalizeSelector(format, { selector , candidate , context , isArbitraryVariant , // 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 =candidate.split(new RegExp(`\\${(ref1 = context === null || context === void 0 ? void 0 : (ref = context.tailwindConfig) === null || ref === void 0 ? void 0 : ref.separator) !== null && ref1 !== void 0 ? ref1 : ":"}(?![^[]*\\])`)).pop() , }) { var ref2; let ast = (0, _postcssSelectorParser.default)().astSync(selector); // We explicitly DO NOT prefix classes in arbitrary variants if ((context === null || context === void 0 ? void 0 : (ref2 = context.tailwindConfig) === null || ref2 === void 0 ? void 0 : ref2.prefix) && !isArbitraryVariant) { format = (0, _prefixSelector.default)(context.tailwindConfig.prefix, format); } format = format.replace(PARENT, `.${(0, _escapeClassName.default)(candidate)}`); let formatAst = (0, _postcssSelectorParser.default)().astSync(format); // Remove extraneous selectors that do not include the base class/candidate being matched against // For example if we have a utility defined `.a, .b { color: red}` // And the formatted variant is sm:b then we want the final selector to be `.sm\:b` and not `.a, .sm\:b` ast.each((sel)=>eliminateIrrelevantSelectors(sel, base)); // 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\\)\\] // ast.walkClasses((node)=>{ if (node.raws && node.value.includes(base)) { node.raws.value = (0, _escapeClassName.default)((0, _unesc.default)(node.raws.value)); } }); 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. ast.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); } 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(); }); // This will make sure to move pseudo's to the correct spot (the end for // pseudo elements) because otherwise the selector will never work // anyway. // // E.g.: // - `before:hover:text-center` would result in `.before\:hover\:text-center:hover::before` // - `hover:before:text-center` would result in `.hover\:before\:text-center:hover::before` // // `::before:hover` doesn't work, which means that we can make it work for you by flipping the order. function collectPseudoElements(selector) { let nodes = []; for (let node of selector.nodes){ if (isPseudoElement(node)) { nodes.push(node); selector.removeChild(node); } if (node === null || node === void 0 ? void 0 : node.nodes) { nodes.push(...collectPseudoElements(node)); } } return nodes; } // Remove unnecessary pseudo selectors that we used as placeholders ast.each((selector)=>{ selector.walkPseudos((p)=>{ if (selectorFunctions.has(p.value)) { p.replaceWith(p.nodes); } }); let pseudoElements = collectPseudoElements(selector); if (pseudoElements.length > 0) { selector.nodes.push(pseudoElements.sort(sortSelector)); } }); return ast.toString(); } // Note: As a rule, double colons (::) should be used instead of a single colon // (:). This distinguishes pseudo-classes from pseudo-elements. However, since // this distinction was not present in older versions of the W3C spec, most // browsers support both syntaxes for the original pseudo-elements. let pseudoElementsBC = [ ":before", ":after", ":first-line", ":first-letter" ]; // These pseudo-elements _can_ be combined with other pseudo selectors AND the order does matter. let pseudoElementExceptions = [ "::file-selector-button" ]; // This will make sure to move pseudo's to the correct spot (the end for // pseudo elements) because otherwise the selector will never work // anyway. // // E.g.: // - `before:hover:text-center` would result in `.before\:hover\:text-center:hover::before` // - `hover:before:text-center` would result in `.hover\:before\:text-center:hover::before` // // `::before:hover` doesn't work, which means that we can make it work // for you by flipping the order. function sortSelector(a, z) { // Both nodes are non-pseudo's so we can safely ignore them and keep // them in the same order. if (a.type !== "pseudo" && z.type !== "pseudo") { return 0; } // If one of them is a combinator, we need to keep it in the same order // because that means it will start a new "section" in the selector. if (a.type === "combinator" ^ z.type === "combinator") { return 0; } // One of the items is a pseudo and the other one isn't. Let's move // the pseudo to the right. if (a.type === "pseudo" ^ z.type === "pseudo") { return (a.type === "pseudo") - (z.type === "pseudo"); } // Both are pseudo's, move the pseudo elements (except for // ::file-selector-button) to the right. return isPseudoElement(a) - isPseudoElement(z); } function isPseudoElement(node) { if (node.type !== "pseudo") return false; if (pseudoElementExceptions.includes(node.value)) return false; return node.value.startsWith("::") || pseudoElementsBC.includes(node.value); } function resolveFunctionArgument(haystack, needle, arg) { let startIdx = haystack.indexOf(arg ? `${needle}(${arg})` : needle); if (startIdx === -1) return null; // Start inside the `(` startIdx += needle.length + 1; let target = ""; let count = 0; for (let char of haystack.slice(startIdx)){ if (char !== "(" && char !== ")") { target += char; } else if (char === "(") { target += char; count++; } else if (char === ")") { if (--count < 0) break; // unbalanced target += char; } } return target; }