source: frontend/node_modules/tailwindcss/src/util/pseudoElements.js

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

Fix frontend appearance

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