You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

201 lines
8.4 KiB

2 years ago
  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. selectorFunctions: ()=>selectorFunctions,
  13. formatVariantSelector: ()=>formatVariantSelector,
  14. finalizeSelector: ()=>finalizeSelector
  15. });
  16. const _postcssSelectorParser = /*#__PURE__*/ _interopRequireDefault(require("postcss-selector-parser"));
  17. const _unesc = /*#__PURE__*/ _interopRequireDefault(require("postcss-selector-parser/dist/util/unesc"));
  18. const _escapeClassName = /*#__PURE__*/ _interopRequireDefault(require("../util/escapeClassName"));
  19. const _prefixSelector = /*#__PURE__*/ _interopRequireDefault(require("../util/prefixSelector"));
  20. function _interopRequireDefault(obj) {
  21. return obj && obj.__esModule ? obj : {
  22. default: obj
  23. };
  24. }
  25. var ref;
  26. let MERGE = ":merge";
  27. let PARENT = "&";
  28. let selectorFunctions = new Set([
  29. MERGE
  30. ]);
  31. function formatVariantSelector(current, ...others) {
  32. for (let other of others){
  33. let incomingValue = resolveFunctionArgument(other, MERGE);
  34. if (incomingValue !== null) {
  35. let existingValue = resolveFunctionArgument(current, MERGE, incomingValue);
  36. if (existingValue !== null) {
  37. let existingTarget = `${MERGE}(${incomingValue})`;
  38. let splitIdx = other.indexOf(existingTarget);
  39. let addition = other.slice(splitIdx + existingTarget.length).split(" ")[0];
  40. current = current.replace(existingTarget, existingTarget + addition);
  41. continue;
  42. }
  43. }
  44. current = other.replace(PARENT, current);
  45. }
  46. return current;
  47. }
  48. var ref1;
  49. function finalizeSelector(format, { selector , candidate , context , isArbitraryVariant , // Split by the separator, but ignore the separator inside square brackets:
  50. //
  51. // E.g.: dark:lg:hover:[paint-order:markers]
  52. // ┬ ┬ ┬ ┬
  53. // │ │ │ ╰── We will not split here
  54. // ╰──┴─────┴─────────────── We will split here
  55. //
  56. 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() , }) {
  57. var ref2;
  58. let ast = (0, _postcssSelectorParser.default)().astSync(selector);
  59. // We explicitly DO NOT prefix classes in arbitrary variants
  60. if ((context === null || context === void 0 ? void 0 : (ref2 = context.tailwindConfig) === null || ref2 === void 0 ? void 0 : ref2.prefix) && !isArbitraryVariant) {
  61. format = (0, _prefixSelector.default)(context.tailwindConfig.prefix, format);
  62. }
  63. format = format.replace(PARENT, `.${(0, _escapeClassName.default)(candidate)}`);
  64. let formatAst = (0, _postcssSelectorParser.default)().astSync(format);
  65. // Remove extraneous selectors that do not include the base class/candidate being matched against
  66. // For example if we have a utility defined `.a, .b { color: red}`
  67. // And the formatted variant is sm:b then we want the final selector to be `.sm\:b` and not `.a, .sm\:b`
  68. ast.each((node)=>{
  69. let hasClassesMatchingCandidate = node.some((n)=>n.type === "class" && n.value === base);
  70. if (!hasClassesMatchingCandidate) {
  71. node.remove();
  72. }
  73. });
  74. // Normalize escaped classes, e.g.:
  75. //
  76. // The idea would be to replace the escaped `base` in the selector with the
  77. // `format`. However, in css you can escape the same selector in a few
  78. // different ways. This would result in different strings and therefore we
  79. // can't replace it properly.
  80. //
  81. // base: bg-[rgb(255,0,0)]
  82. // base in selector: bg-\\[rgb\\(255\\,0\\,0\\)\\]
  83. // escaped base: bg-\\[rgb\\(255\\2c 0\\2c 0\\)\\]
  84. //
  85. ast.walkClasses((node)=>{
  86. if (node.raws && node.value.includes(base)) {
  87. node.raws.value = (0, _escapeClassName.default)((0, _unesc.default)(node.raws.value));
  88. }
  89. });
  90. // We can safely replace the escaped base now, since the `base` section is
  91. // now in a normalized escaped value.
  92. ast.walkClasses((node)=>{
  93. if (node.value === base) {
  94. node.replaceWith(...formatAst.nodes);
  95. }
  96. });
  97. // This will make sure to move pseudo's to the correct spot (the end for
  98. // pseudo elements) because otherwise the selector will never work
  99. // anyway.
  100. //
  101. // E.g.:
  102. // - `before:hover:text-center` would result in `.before\:hover\:text-center:hover::before`
  103. // - `hover:before:text-center` would result in `.hover\:before\:text-center:hover::before`
  104. //
  105. // `::before:hover` doesn't work, which means that we can make it work for you by flipping the order.
  106. function collectPseudoElements(selector) {
  107. let nodes = [];
  108. for (let node of selector.nodes){
  109. if (isPseudoElement(node)) {
  110. nodes.push(node);
  111. selector.removeChild(node);
  112. }
  113. if (node === null || node === void 0 ? void 0 : node.nodes) {
  114. nodes.push(...collectPseudoElements(node));
  115. }
  116. }
  117. return nodes;
  118. }
  119. // Remove unnecessary pseudo selectors that we used as placeholders
  120. ast.each((selector)=>{
  121. selector.walkPseudos((p)=>{
  122. if (selectorFunctions.has(p.value)) {
  123. p.replaceWith(p.nodes);
  124. }
  125. });
  126. let pseudoElements = collectPseudoElements(selector);
  127. if (pseudoElements.length > 0) {
  128. selector.nodes.push(pseudoElements.sort(sortSelector));
  129. }
  130. });
  131. return ast.toString();
  132. }
  133. // Note: As a rule, double colons (::) should be used instead of a single colon
  134. // (:). This distinguishes pseudo-classes from pseudo-elements. However, since
  135. // this distinction was not present in older versions of the W3C spec, most
  136. // browsers support both syntaxes for the original pseudo-elements.
  137. let pseudoElementsBC = [
  138. ":before",
  139. ":after",
  140. ":first-line",
  141. ":first-letter"
  142. ];
  143. // These pseudo-elements _can_ be combined with other pseudo selectors AND the order does matter.
  144. let pseudoElementExceptions = [
  145. "::file-selector-button"
  146. ];
  147. // This will make sure to move pseudo's to the correct spot (the end for
  148. // pseudo elements) because otherwise the selector will never work
  149. // anyway.
  150. //
  151. // E.g.:
  152. // - `before:hover:text-center` would result in `.before\:hover\:text-center:hover::before`
  153. // - `hover:before:text-center` would result in `.hover\:before\:text-center:hover::before`
  154. //
  155. // `::before:hover` doesn't work, which means that we can make it work
  156. // for you by flipping the order.
  157. function sortSelector(a, z) {
  158. // Both nodes are non-pseudo's so we can safely ignore them and keep
  159. // them in the same order.
  160. if (a.type !== "pseudo" && z.type !== "pseudo") {
  161. return 0;
  162. }
  163. // If one of them is a combinator, we need to keep it in the same order
  164. // because that means it will start a new "section" in the selector.
  165. if (a.type === "combinator" ^ z.type === "combinator") {
  166. return 0;
  167. }
  168. // One of the items is a pseudo and the other one isn't. Let's move
  169. // the pseudo to the right.
  170. if (a.type === "pseudo" ^ z.type === "pseudo") {
  171. return (a.type === "pseudo") - (z.type === "pseudo");
  172. }
  173. // Both are pseudo's, move the pseudo elements (except for
  174. // ::file-selector-button) to the right.
  175. return isPseudoElement(a) - isPseudoElement(z);
  176. }
  177. function isPseudoElement(node) {
  178. if (node.type !== "pseudo") return false;
  179. if (pseudoElementExceptions.includes(node.value)) return false;
  180. return node.value.startsWith("::") || pseudoElementsBC.includes(node.value);
  181. }
  182. function resolveFunctionArgument(haystack, needle, arg) {
  183. let startIdx = haystack.indexOf(arg ? `${needle}(${arg})` : needle);
  184. if (startIdx === -1) return null;
  185. // Start inside the `(`
  186. startIdx += needle.length + 1;
  187. let target = "";
  188. let count = 0;
  189. for (let char of haystack.slice(startIdx)){
  190. if (char !== "(" && char !== ")") {
  191. target += char;
  192. } else if (char === "(") {
  193. target += char;
  194. count++;
  195. } else if (char === ")") {
  196. if (--count < 0) break; // unbalanced
  197. target += char;
  198. }
  199. }
  200. return target;
  201. }