source: frontend/node_modules/tailwindcss/lib/util/formatVariantSelector.js

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 12 days ago

Fix frontend appearance

  • Property mode set to 100644
File size: 11.3 KB
Line 
1"use strict";
2Object.defineProperty(exports, "__esModule", {
3 value: true
4});
5function _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});
25const _postcssselectorparser = /*#__PURE__*/ _interop_require_default(require("postcss-selector-parser"));
26const _unesc = /*#__PURE__*/ _interop_require_default(require("postcss-selector-parser/dist/util/unesc"));
27const _escapeClassName = /*#__PURE__*/ _interop_require_default(require("../util/escapeClassName"));
28const _prefixSelector = /*#__PURE__*/ _interop_require_default(require("../util/prefixSelector"));
29const _pseudoElements = require("./pseudoElements");
30const _splitAtTopLevelOnly = require("./splitAtTopLevelOnly");
31function _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";
37function 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}
112function 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}
131function 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}
223function 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}
Note: See TracBrowser for help on using the repository browser.