Нет описания

pseudoElements.js 7.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  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 :before and :after variants.
  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. "::after": [
  27. "terminal",
  28. "jumpable"
  29. ],
  30. "::backdrop": [
  31. "terminal"
  32. ],
  33. "::before": [
  34. "terminal",
  35. "jumpable"
  36. ],
  37. "::cue": [
  38. "terminal"
  39. ],
  40. "::cue-region": [
  41. "terminal"
  42. ],
  43. "::first-letter": [
  44. "terminal",
  45. "jumpable"
  46. ],
  47. "::first-line": [
  48. "terminal",
  49. "jumpable"
  50. ],
  51. "::grammar-error": [
  52. "terminal"
  53. ],
  54. "::marker": [
  55. "terminal"
  56. ],
  57. "::part": [
  58. "terminal",
  59. "actionable"
  60. ],
  61. "::placeholder": [
  62. "terminal"
  63. ],
  64. "::selection": [
  65. "terminal"
  66. ],
  67. "::slotted": [
  68. "terminal"
  69. ],
  70. "::spelling-error": [
  71. "terminal"
  72. ],
  73. "::target-text": [
  74. "terminal"
  75. ],
  76. // other
  77. "::file-selector-button": [
  78. "terminal",
  79. "actionable"
  80. ],
  81. "::-webkit-progress-bar": [
  82. "terminal",
  83. "actionable"
  84. ],
  85. // Webkit scroll bar pseudo elements can be combined with user-action pseudo classes
  86. "::-webkit-scrollbar": [
  87. "terminal",
  88. "actionable"
  89. ],
  90. "::-webkit-scrollbar-button": [
  91. "terminal",
  92. "actionable"
  93. ],
  94. "::-webkit-scrollbar-thumb": [
  95. "terminal",
  96. "actionable"
  97. ],
  98. "::-webkit-scrollbar-track": [
  99. "terminal",
  100. "actionable"
  101. ],
  102. "::-webkit-scrollbar-track-piece": [
  103. "terminal",
  104. "actionable"
  105. ],
  106. "::-webkit-scrollbar-corner": [
  107. "terminal",
  108. "actionable"
  109. ],
  110. "::-webkit-resizer": [
  111. "terminal",
  112. "actionable"
  113. ],
  114. // Note: As a rule, double colons (::) should be used instead of a single colon
  115. // (:). This distinguishes pseudo-classes from pseudo-elements. However, since
  116. // this distinction was not present in older versions of the W3C spec, most
  117. // browsers support both syntaxes for the original pseudo-elements.
  118. ":after": [
  119. "terminal",
  120. "jumpable"
  121. ],
  122. ":before": [
  123. "terminal",
  124. "jumpable"
  125. ],
  126. ":first-letter": [
  127. "terminal",
  128. "jumpable"
  129. ],
  130. ":first-line": [
  131. "terminal",
  132. "jumpable"
  133. ],
  134. // The default value is used when the pseudo-element is not recognized
  135. // Because it's not recognized, we don't know if it's terminal or not
  136. // So we assume it can't be moved AND can have user-action pseudo classes attached to it
  137. __default__: [
  138. "actionable"
  139. ]
  140. };
  141. function movePseudos(sel) {
  142. let [pseudos] = movablePseudos(sel);
  143. // Remove all pseudo elements from their respective selectors
  144. pseudos.forEach(([sel, pseudo])=>sel.removeChild(pseudo));
  145. // Re-add them to the end of the selector in the correct order.
  146. // This moves terminal pseudo elements to the end of the
  147. // selector otherwise the selector will not be valid.
  148. //
  149. // Examples:
  150. // - `before:hover:text-center` would result in `.before\:hover\:text-center:hover::before`
  151. // - `hover:before:text-center` would result in `.hover\:before\:text-center:hover::before`
  152. //
  153. // The selector `::before:hover` does not work but we
  154. // can make it work for you by flipping the order.
  155. sel.nodes.push(...pseudos.map(([, pseudo])=>pseudo));
  156. return sel;
  157. }
  158. /** @typedef {[sel: Selector, pseudo: Pseudo, attachedTo: Pseudo | null]} MovablePseudo */ /** @typedef {[pseudos: MovablePseudo[], lastSeenElement: Pseudo | null]} MovablePseudosResult */ /**
  159. * @param {Selector} sel
  160. * @returns {MovablePseudosResult}
  161. */ function movablePseudos(sel) {
  162. /** @type {MovablePseudo[]} */ let buffer = [];
  163. /** @type {Pseudo | null} */ let lastSeenElement = null;
  164. for (let node of sel.nodes){
  165. if (node.type === "combinator") {
  166. buffer = buffer.filter(([, node])=>propertiesForPseudo(node).includes("jumpable"));
  167. lastSeenElement = null;
  168. } else if (node.type === "pseudo") {
  169. if (isMovablePseudoElement(node)) {
  170. lastSeenElement = node;
  171. buffer.push([
  172. sel,
  173. node,
  174. null
  175. ]);
  176. } else if (lastSeenElement && isAttachablePseudoClass(node, lastSeenElement)) {
  177. buffer.push([
  178. sel,
  179. node,
  180. lastSeenElement
  181. ]);
  182. } else {
  183. lastSeenElement = null;
  184. }
  185. var _node_nodes;
  186. for (let sub of (_node_nodes = node.nodes) !== null && _node_nodes !== void 0 ? _node_nodes : []){
  187. let [movable, lastSeenElementInSub] = movablePseudos(sub);
  188. lastSeenElement = lastSeenElementInSub || lastSeenElement;
  189. buffer.push(...movable);
  190. }
  191. }
  192. }
  193. return [
  194. buffer,
  195. lastSeenElement
  196. ];
  197. }
  198. /**
  199. * @param {Node} node
  200. * @returns {boolean}
  201. */ function isPseudoElement(node) {
  202. return node.value.startsWith("::") || elementProperties[node.value] !== undefined;
  203. }
  204. /**
  205. * @param {Node} node
  206. * @returns {boolean}
  207. */ function isMovablePseudoElement(node) {
  208. return isPseudoElement(node) && propertiesForPseudo(node).includes("terminal");
  209. }
  210. /**
  211. * @param {Node} node
  212. * @param {Pseudo} pseudo
  213. * @returns {boolean}
  214. */ function isAttachablePseudoClass(node, pseudo) {
  215. if (node.type !== "pseudo") return false;
  216. if (isPseudoElement(node)) return false;
  217. return propertiesForPseudo(pseudo).includes("actionable");
  218. }
  219. /**
  220. * @param {Pseudo} pseudo
  221. * @returns {PseudoProperty[]}
  222. */ function propertiesForPseudo(pseudo) {
  223. var _elementProperties_pseudo_value;
  224. return (_elementProperties_pseudo_value = elementProperties[pseudo.value]) !== null && _elementProperties_pseudo_value !== void 0 ? _elementProperties_pseudo_value : elementProperties.__default__;
  225. }