pseudoElements.js 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. /** @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 */ // There are some pseudo-elements that may or may not be:
  2. // **Actionable**
  3. // Zero or more user-action pseudo-classes may be attached to the pseudo-element itself
  4. // structural-pseudo-classes are NOT allowed but we don't make
  5. // The spec is not clear on whether this is allowed or not — but in practice it is.
  6. // **Terminal**
  7. // It MUST be placed at the end of a selector
  8. //
  9. // This is the required in the spec. However, some pseudo elements are not "terminal" because
  10. // they represent a "boundary piercing" that is compiled out by a build step.
  11. // **Jumpable**
  12. // Any terminal element may "jump" over combinators when moving to the end of the selector
  13. //
  14. // This is a backwards-compat quirk of pseudo element variants from earlier versions of Tailwind CSS.
  15. /** @typedef {'terminal' | 'actionable' | 'jumpable'} PseudoProperty */ /** @type {Record<string, PseudoProperty[]>} */ "use strict";
  16. Object.defineProperty(exports, "__esModule", {
  17. value: true
  18. });
  19. Object.defineProperty(exports, "movePseudos", {
  20. enumerable: true,
  21. get: function() {
  22. return movePseudos;
  23. }
  24. });
  25. let elementProperties = {
  26. // Pseudo elements from the spec
  27. "::after": [
  28. "terminal",
  29. "jumpable"
  30. ],
  31. "::backdrop": [
  32. "terminal",
  33. "jumpable"
  34. ],
  35. "::before": [
  36. "terminal",
  37. "jumpable"
  38. ],
  39. "::cue": [
  40. "terminal"
  41. ],
  42. "::cue-region": [
  43. "terminal"
  44. ],
  45. "::first-letter": [
  46. "terminal",
  47. "jumpable"
  48. ],
  49. "::first-line": [
  50. "terminal",
  51. "jumpable"
  52. ],
  53. "::grammar-error": [
  54. "terminal"
  55. ],
  56. "::marker": [
  57. "terminal",
  58. "jumpable"
  59. ],
  60. "::part": [
  61. "terminal",
  62. "actionable"
  63. ],
  64. "::placeholder": [
  65. "terminal",
  66. "jumpable"
  67. ],
  68. "::selection": [
  69. "terminal",
  70. "jumpable"
  71. ],
  72. "::slotted": [
  73. "terminal"
  74. ],
  75. "::spelling-error": [
  76. "terminal"
  77. ],
  78. "::target-text": [
  79. "terminal"
  80. ],
  81. // Pseudo elements from the spec with special rules
  82. "::file-selector-button": [
  83. "terminal",
  84. "actionable"
  85. ],
  86. // Library-specific pseudo elements used by component libraries
  87. // These are Shadow DOM-like
  88. "::deep": [
  89. "actionable"
  90. ],
  91. "::v-deep": [
  92. "actionable"
  93. ],
  94. "::ng-deep": [
  95. "actionable"
  96. ],
  97. // Note: As a rule, double colons (::) should be used instead of a single colon
  98. // (:). This distinguishes pseudo-classes from pseudo-elements. However, since
  99. // this distinction was not present in older versions of the W3C spec, most
  100. // browsers support both syntaxes for the original pseudo-elements.
  101. ":after": [
  102. "terminal",
  103. "jumpable"
  104. ],
  105. ":before": [
  106. "terminal",
  107. "jumpable"
  108. ],
  109. ":first-letter": [
  110. "terminal",
  111. "jumpable"
  112. ],
  113. ":first-line": [
  114. "terminal",
  115. "jumpable"
  116. ],
  117. ":where": [],
  118. ":is": [],
  119. ":has": [],
  120. // The default value is used when the pseudo-element is not recognized
  121. // Because it's not recognized, we don't know if it's terminal or not
  122. // So we assume it can be moved AND can have user-action pseudo classes attached to it
  123. __default__: [
  124. "terminal",
  125. "actionable"
  126. ]
  127. };
  128. function movePseudos(sel) {
  129. let [pseudos] = movablePseudos(sel);
  130. // Remove all pseudo elements from their respective selectors
  131. pseudos.forEach(([sel, pseudo])=>sel.removeChild(pseudo));
  132. // Re-add them to the end of the selector in the correct order.
  133. // This moves terminal pseudo elements to the end of the
  134. // selector otherwise the selector will not be valid.
  135. //
  136. // Examples:
  137. // - `before:hover:text-center` would result in `.before\:hover\:text-center:hover::before`
  138. // - `hover:before:text-center` would result in `.hover\:before\:text-center:hover::before`
  139. //
  140. // The selector `::before:hover` does not work but we
  141. // can make it work for you by flipping the order.
  142. sel.nodes.push(...pseudos.map(([, pseudo])=>pseudo));
  143. return sel;
  144. }
  145. /** @typedef {[sel: Selector, pseudo: Pseudo, attachedTo: Pseudo | null]} MovablePseudo */ /** @typedef {[pseudos: MovablePseudo[], lastSeenElement: Pseudo | null]} MovablePseudosResult */ /**
  146. * @param {Selector} sel
  147. * @returns {MovablePseudosResult}
  148. */ function movablePseudos(sel) {
  149. /** @type {MovablePseudo[]} */ let buffer = [];
  150. /** @type {Pseudo | null} */ let lastSeenElement = null;
  151. for (let node of sel.nodes){
  152. if (node.type === "combinator") {
  153. buffer = buffer.filter(([, node])=>propertiesForPseudo(node).includes("jumpable"));
  154. lastSeenElement = null;
  155. } else if (node.type === "pseudo") {
  156. if (isMovablePseudoElement(node)) {
  157. lastSeenElement = node;
  158. buffer.push([
  159. sel,
  160. node,
  161. null
  162. ]);
  163. } else if (lastSeenElement && isAttachablePseudoClass(node, lastSeenElement)) {
  164. buffer.push([
  165. sel,
  166. node,
  167. lastSeenElement
  168. ]);
  169. } else {
  170. lastSeenElement = null;
  171. }
  172. var _node_nodes;
  173. for (let sub of (_node_nodes = node.nodes) !== null && _node_nodes !== void 0 ? _node_nodes : []){
  174. let [movable, lastSeenElementInSub] = movablePseudos(sub);
  175. lastSeenElement = lastSeenElementInSub || lastSeenElement;
  176. buffer.push(...movable);
  177. }
  178. }
  179. }
  180. return [
  181. buffer,
  182. lastSeenElement
  183. ];
  184. }
  185. /**
  186. * @param {Node} node
  187. * @returns {boolean}
  188. */ function isPseudoElement(node) {
  189. return node.value.startsWith("::") || elementProperties[node.value] !== undefined;
  190. }
  191. /**
  192. * @param {Node} node
  193. * @returns {boolean}
  194. */ function isMovablePseudoElement(node) {
  195. return isPseudoElement(node) && propertiesForPseudo(node).includes("terminal");
  196. }
  197. /**
  198. * @param {Node} node
  199. * @param {Pseudo} pseudo
  200. * @returns {boolean}
  201. */ function isAttachablePseudoClass(node, pseudo) {
  202. if (node.type !== "pseudo") return false;
  203. if (isPseudoElement(node)) return false;
  204. return propertiesForPseudo(pseudo).includes("actionable");
  205. }
  206. /**
  207. * @param {Pseudo} pseudo
  208. * @returns {PseudoProperty[]}
  209. */ function propertiesForPseudo(pseudo) {
  210. var _elementProperties_pseudo_value;
  211. return (_elementProperties_pseudo_value = elementProperties[pseudo.value]) !== null && _elementProperties_pseudo_value !== void 0 ? _elementProperties_pseudo_value : elementProperties.__default__;
  212. }