| 1 | "use strict";
|
|---|
| 2 | Object.defineProperty(exports, "__esModule", {
|
|---|
| 3 | value: true
|
|---|
| 4 | });
|
|---|
| 5 | function _export(target, all) {
|
|---|
| 6 | for(var name in all)Object.defineProperty(target, name, {
|
|---|
| 7 | enumerable: true,
|
|---|
| 8 | get: all[name]
|
|---|
| 9 | });
|
|---|
| 10 | }
|
|---|
| 11 | _export(exports, {
|
|---|
| 12 | formatVariantSelector: function() {
|
|---|
| 13 | return formatVariantSelector;
|
|---|
| 14 | },
|
|---|
| 15 | eliminateIrrelevantSelectors: function() {
|
|---|
| 16 | return eliminateIrrelevantSelectors;
|
|---|
| 17 | },
|
|---|
| 18 | finalizeSelector: function() {
|
|---|
| 19 | return finalizeSelector;
|
|---|
| 20 | },
|
|---|
| 21 | handleMergePseudo: function() {
|
|---|
| 22 | return handleMergePseudo;
|
|---|
| 23 | }
|
|---|
| 24 | });
|
|---|
| 25 | const _postcssselectorparser = /*#__PURE__*/ _interop_require_default(require("postcss-selector-parser"));
|
|---|
| 26 | const _unesc = /*#__PURE__*/ _interop_require_default(require("postcss-selector-parser/dist/util/unesc"));
|
|---|
| 27 | const _escapeClassName = /*#__PURE__*/ _interop_require_default(require("../util/escapeClassName"));
|
|---|
| 28 | const _prefixSelector = /*#__PURE__*/ _interop_require_default(require("../util/prefixSelector"));
|
|---|
| 29 | const _pseudoElements = require("./pseudoElements");
|
|---|
| 30 | const _splitAtTopLevelOnly = require("./splitAtTopLevelOnly");
|
|---|
| 31 | function _interop_require_default(obj) {
|
|---|
| 32 | return obj && obj.__esModule ? obj : {
|
|---|
| 33 | default: obj
|
|---|
| 34 | };
|
|---|
| 35 | }
|
|---|
| 36 | /** @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";
|
|---|
| 37 | function formatVariantSelector(formats, { context , candidate }) {
|
|---|
| 38 | var _context_tailwindConfig_prefix;
|
|---|
| 39 | let prefix = (_context_tailwindConfig_prefix = context === null || context === void 0 ? void 0 : context.tailwindConfig.prefix) !== null && _context_tailwindConfig_prefix !== void 0 ? _context_tailwindConfig_prefix : "";
|
|---|
| 40 | // Parse the format selector into an AST
|
|---|
| 41 | let parsedFormats = formats.map((format)=>{
|
|---|
| 42 | let ast = (0, _postcssselectorparser.default)().astSync(format.format);
|
|---|
| 43 | return {
|
|---|
| 44 | ...format,
|
|---|
| 45 | ast: format.respectPrefix ? (0, _prefixSelector.default)(prefix, ast) : ast
|
|---|
| 46 | };
|
|---|
| 47 | });
|
|---|
| 48 | // We start with the candidate selector
|
|---|
| 49 | let formatAst = _postcssselectorparser.default.root({
|
|---|
| 50 | nodes: [
|
|---|
| 51 | _postcssselectorparser.default.selector({
|
|---|
| 52 | nodes: [
|
|---|
| 53 | _postcssselectorparser.default.className({
|
|---|
| 54 | value: (0, _escapeClassName.default)(candidate)
|
|---|
| 55 | })
|
|---|
| 56 | ]
|
|---|
| 57 | })
|
|---|
| 58 | ]
|
|---|
| 59 | });
|
|---|
| 60 | // And iteratively merge each format selector into the candidate selector
|
|---|
| 61 | for (let { ast } of parsedFormats){
|
|---|
| 62 | [formatAst, ast] = handleMergePseudo(formatAst, ast);
|
|---|
| 63 | // 2. Merge the format selector into the current selector AST
|
|---|
| 64 | ast.walkNesting((nesting)=>nesting.replaceWith(...formatAst.nodes[0].nodes));
|
|---|
| 65 | // 3. Keep going!
|
|---|
| 66 | formatAst = ast;
|
|---|
| 67 | }
|
|---|
| 68 | return formatAst;
|
|---|
| 69 | }
|
|---|
| 70 | /**
|
|---|
| 71 | * Given any node in a selector this gets the "simple" selector it's a part of
|
|---|
| 72 | * A simple selector is just a list of nodes without any combinators
|
|---|
| 73 | * Technically :is(), :not(), :has(), etc… can have combinators but those are nested
|
|---|
| 74 | * inside the relevant node and won't be picked up so they're fine to ignore
|
|---|
| 75 | *
|
|---|
| 76 | * @param {Node} node
|
|---|
| 77 | * @returns {Node[]}
|
|---|
| 78 | **/ function simpleSelectorForNode(node) {
|
|---|
| 79 | /** @type {Node[]} */ let nodes = [];
|
|---|
| 80 | // Walk backwards until we hit a combinator node (or the start)
|
|---|
| 81 | while(node.prev() && node.prev().type !== "combinator"){
|
|---|
| 82 | node = node.prev();
|
|---|
| 83 | }
|
|---|
| 84 | // Now record all non-combinator nodes until we hit one (or the end)
|
|---|
| 85 | while(node && node.type !== "combinator"){
|
|---|
| 86 | nodes.push(node);
|
|---|
| 87 | node = node.next();
|
|---|
| 88 | }
|
|---|
| 89 | return nodes;
|
|---|
| 90 | }
|
|---|
| 91 | /**
|
|---|
| 92 | * Resorts the nodes in a selector to ensure they're in the correct order
|
|---|
| 93 | * Tags go before classes, and pseudo classes go after classes
|
|---|
| 94 | *
|
|---|
| 95 | * @param {Selector} sel
|
|---|
| 96 | * @returns {Selector}
|
|---|
| 97 | **/ function resortSelector(sel) {
|
|---|
| 98 | sel.sort((a, b)=>{
|
|---|
| 99 | if (a.type === "tag" && b.type === "class") {
|
|---|
| 100 | return -1;
|
|---|
| 101 | } else if (a.type === "class" && b.type === "tag") {
|
|---|
| 102 | return 1;
|
|---|
| 103 | } else if (a.type === "class" && b.type === "pseudo" && b.value.startsWith("::")) {
|
|---|
| 104 | return -1;
|
|---|
| 105 | } else if (a.type === "pseudo" && a.value.startsWith("::") && b.type === "class") {
|
|---|
| 106 | return 1;
|
|---|
| 107 | }
|
|---|
| 108 | return sel.index(a) - sel.index(b);
|
|---|
| 109 | });
|
|---|
| 110 | return sel;
|
|---|
| 111 | }
|
|---|
| 112 | function eliminateIrrelevantSelectors(sel, base) {
|
|---|
| 113 | let hasClassesMatchingCandidate = false;
|
|---|
| 114 | sel.walk((child)=>{
|
|---|
| 115 | if (child.type === "class" && child.value === base) {
|
|---|
| 116 | hasClassesMatchingCandidate = true;
|
|---|
| 117 | return false // Stop walking
|
|---|
| 118 | ;
|
|---|
| 119 | }
|
|---|
| 120 | });
|
|---|
| 121 | if (!hasClassesMatchingCandidate) {
|
|---|
| 122 | sel.remove();
|
|---|
| 123 | }
|
|---|
| 124 | // We do NOT recursively eliminate sub selectors that don't have the base class
|
|---|
| 125 | // as this is NOT a safe operation. For example, if we have:
|
|---|
| 126 | // `.space-x-2 > :not([hidden]) ~ :not([hidden])`
|
|---|
| 127 | // We cannot remove the [hidden] from the :not() because it would change the
|
|---|
| 128 | // meaning of the selector.
|
|---|
| 129 | // TODO: Can we do this for :matches, :is, and :where?
|
|---|
| 130 | }
|
|---|
| 131 | function finalizeSelector(current, formats, { context , candidate , base }) {
|
|---|
| 132 | var _context_tailwindConfig;
|
|---|
| 133 | var _context_tailwindConfig_separator;
|
|---|
| 134 | 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 : ":";
|
|---|
| 135 | // Split by the separator, but ignore the separator inside square brackets:
|
|---|
| 136 | //
|
|---|
| 137 | // E.g.: dark:lg:hover:[paint-order:markers]
|
|---|
| 138 | // ┬ ┬ ┬ ┬
|
|---|
| 139 | // │ │ │ ╰── We will not split here
|
|---|
| 140 | // ╰──┴─────┴─────────────── We will split here
|
|---|
| 141 | //
|
|---|
| 142 | base = base !== null && base !== void 0 ? base : (0, _splitAtTopLevelOnly.splitAtTopLevelOnly)(candidate, separator).pop();
|
|---|
| 143 | // Parse the selector into an AST
|
|---|
| 144 | let selector = (0, _postcssselectorparser.default)().astSync(current);
|
|---|
| 145 | // Normalize escaped classes, e.g.:
|
|---|
| 146 | //
|
|---|
| 147 | // The idea would be to replace the escaped `base` in the selector with the
|
|---|
| 148 | // `format`. However, in css you can escape the same selector in a few
|
|---|
| 149 | // different ways. This would result in different strings and therefore we
|
|---|
| 150 | // can't replace it properly.
|
|---|
| 151 | //
|
|---|
| 152 | // base: bg-[rgb(255,0,0)]
|
|---|
| 153 | // base in selector: bg-\\[rgb\\(255\\,0\\,0\\)\\]
|
|---|
| 154 | // escaped base: bg-\\[rgb\\(255\\2c 0\\2c 0\\)\\]
|
|---|
| 155 | //
|
|---|
| 156 | selector.walkClasses((node)=>{
|
|---|
| 157 | if (node.raws && node.value.includes(base)) {
|
|---|
| 158 | node.raws.value = (0, _escapeClassName.default)((0, _unesc.default)(node.raws.value));
|
|---|
| 159 | }
|
|---|
| 160 | });
|
|---|
| 161 | // Remove extraneous selectors that do not include the base candidate
|
|---|
| 162 | selector.each((sel)=>eliminateIrrelevantSelectors(sel, base));
|
|---|
| 163 | // If ffter eliminating irrelevant selectors, we end up with nothing
|
|---|
| 164 | // Then the whole "rule" this is associated with does not need to exist
|
|---|
| 165 | // We use `null` as a marker value for that case
|
|---|
| 166 | if (selector.length === 0) {
|
|---|
| 167 | return null;
|
|---|
| 168 | }
|
|---|
| 169 | // If there are no formats that means there were no variants added to the candidate
|
|---|
| 170 | // so we can just return the selector as-is
|
|---|
| 171 | let formatAst = Array.isArray(formats) ? formatVariantSelector(formats, {
|
|---|
| 172 | context,
|
|---|
| 173 | candidate
|
|---|
| 174 | }) : formats;
|
|---|
| 175 | if (formatAst === null) {
|
|---|
| 176 | return selector.toString();
|
|---|
| 177 | }
|
|---|
| 178 | let simpleStart = _postcssselectorparser.default.comment({
|
|---|
| 179 | value: "/*__simple__*/"
|
|---|
| 180 | });
|
|---|
| 181 | let simpleEnd = _postcssselectorparser.default.comment({
|
|---|
| 182 | value: "/*__simple__*/"
|
|---|
| 183 | });
|
|---|
| 184 | // We can safely replace the escaped base now, since the `base` section is
|
|---|
| 185 | // now in a normalized escaped value.
|
|---|
| 186 | selector.walkClasses((node)=>{
|
|---|
| 187 | if (node.value !== base) {
|
|---|
| 188 | return;
|
|---|
| 189 | }
|
|---|
| 190 | let parent = node.parent;
|
|---|
| 191 | let formatNodes = formatAst.nodes[0].nodes;
|
|---|
| 192 | // Perf optimization: if the parent is a single class we can just replace it and be done
|
|---|
| 193 | if (parent.nodes.length === 1) {
|
|---|
| 194 | node.replaceWith(...formatNodes);
|
|---|
| 195 | return;
|
|---|
| 196 | }
|
|---|
| 197 | let simpleSelector = simpleSelectorForNode(node);
|
|---|
| 198 | parent.insertBefore(simpleSelector[0], simpleStart);
|
|---|
| 199 | parent.insertAfter(simpleSelector[simpleSelector.length - 1], simpleEnd);
|
|---|
| 200 | for (let child of formatNodes){
|
|---|
| 201 | parent.insertBefore(simpleSelector[0], child.clone());
|
|---|
| 202 | }
|
|---|
| 203 | node.remove();
|
|---|
| 204 | // Re-sort the simple selector to ensure it's in the correct order
|
|---|
| 205 | simpleSelector = simpleSelectorForNode(simpleStart);
|
|---|
| 206 | let firstNode = parent.index(simpleStart);
|
|---|
| 207 | parent.nodes.splice(firstNode, simpleSelector.length, ...resortSelector(_postcssselectorparser.default.selector({
|
|---|
| 208 | nodes: simpleSelector
|
|---|
| 209 | })).nodes);
|
|---|
| 210 | simpleStart.remove();
|
|---|
| 211 | simpleEnd.remove();
|
|---|
| 212 | });
|
|---|
| 213 | // Remove unnecessary pseudo selectors that we used as placeholders
|
|---|
| 214 | selector.walkPseudos((p)=>{
|
|---|
| 215 | if (p.value === MERGE) {
|
|---|
| 216 | p.replaceWith(p.nodes);
|
|---|
| 217 | }
|
|---|
| 218 | });
|
|---|
| 219 | // Move pseudo elements to the end of the selector (if necessary)
|
|---|
| 220 | selector.each((sel)=>(0, _pseudoElements.movePseudos)(sel));
|
|---|
| 221 | return selector.toString();
|
|---|
| 222 | }
|
|---|
| 223 | function handleMergePseudo(selector, format) {
|
|---|
| 224 | /** @type {{pseudo: Pseudo, value: string}[]} */ let merges = [];
|
|---|
| 225 | // Find all :merge() pseudo-classes in `selector`
|
|---|
| 226 | selector.walkPseudos((pseudo)=>{
|
|---|
| 227 | if (pseudo.value === MERGE) {
|
|---|
| 228 | merges.push({
|
|---|
| 229 | pseudo,
|
|---|
| 230 | value: pseudo.nodes[0].toString()
|
|---|
| 231 | });
|
|---|
| 232 | }
|
|---|
| 233 | });
|
|---|
| 234 | // Find all :merge() "attachments" in `format` and attach them to the matching selector in `selector`
|
|---|
| 235 | format.walkPseudos((pseudo)=>{
|
|---|
| 236 | if (pseudo.value !== MERGE) {
|
|---|
| 237 | return;
|
|---|
| 238 | }
|
|---|
| 239 | let value = pseudo.nodes[0].toString();
|
|---|
| 240 | // Does `selector` contain a :merge() pseudo-class with the same value?
|
|---|
| 241 | let existing = merges.find((merge)=>merge.value === value);
|
|---|
| 242 | // Nope so there's nothing to do
|
|---|
| 243 | if (!existing) {
|
|---|
| 244 | return;
|
|---|
| 245 | }
|
|---|
| 246 | // Everything after `:merge()` up to the next combinator is what is attached to the merged selector
|
|---|
| 247 | let attachments = [];
|
|---|
| 248 | let next = pseudo.next();
|
|---|
| 249 | while(next && next.type !== "combinator"){
|
|---|
| 250 | attachments.push(next);
|
|---|
| 251 | next = next.next();
|
|---|
| 252 | }
|
|---|
| 253 | let combinator = next;
|
|---|
| 254 | existing.pseudo.parent.insertAfter(existing.pseudo, _postcssselectorparser.default.selector({
|
|---|
| 255 | nodes: attachments.map((node)=>node.clone())
|
|---|
| 256 | }));
|
|---|
| 257 | pseudo.remove();
|
|---|
| 258 | attachments.forEach((node)=>node.remove());
|
|---|
| 259 | // What about this case:
|
|---|
| 260 | // :merge(.group):focus > &
|
|---|
| 261 | // :merge(.group):hover &
|
|---|
| 262 | if (combinator && combinator.type === "combinator") {
|
|---|
| 263 | combinator.remove();
|
|---|
| 264 | }
|
|---|
| 265 | });
|
|---|
| 266 | return [
|
|---|
| 267 | selector,
|
|---|
| 268 | format
|
|---|
| 269 | ];
|
|---|
| 270 | }
|
|---|