tailwindcss 0.0.0-insiders.ea10bb9 → 0.0.0-insiders.ea4e1cd

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (184) hide show
  1. package/README.md +1 -1
  2. package/lib/cli/build/deps.js +17 -9
  3. package/lib/cli/build/index.js +20 -10
  4. package/lib/cli/build/plugin.js +86 -70
  5. package/lib/cli/build/utils.js +18 -8
  6. package/lib/cli/build/watching.js +16 -12
  7. package/lib/cli/help/index.js +6 -4
  8. package/lib/cli/index.js +222 -10
  9. package/lib/cli/init/index.js +29 -12
  10. package/lib/cli-peer-dependencies.js +12 -4
  11. package/lib/cli.js +4 -226
  12. package/lib/corePluginList.js +7 -1
  13. package/lib/corePlugins.js +348 -75
  14. package/lib/css/preflight.css +2 -0
  15. package/lib/featureFlags.js +27 -15
  16. package/lib/index.js +1 -47
  17. package/lib/lib/cacheInvalidation.js +7 -5
  18. package/lib/lib/collapseAdjacentRules.js +5 -3
  19. package/lib/lib/collapseDuplicateDeclarations.js +12 -10
  20. package/lib/lib/content.js +34 -30
  21. package/lib/lib/defaultExtractor.js +15 -8
  22. package/lib/lib/detectNesting.js +10 -2
  23. package/lib/lib/evaluateTailwindFunctions.js +22 -20
  24. package/lib/lib/expandApplyAtRules.js +43 -35
  25. package/lib/lib/expandTailwindAtRules.js +46 -19
  26. package/lib/lib/findAtConfigPath.js +9 -7
  27. package/lib/lib/generateRules.js +157 -92
  28. package/lib/lib/getModuleDependencies.js +85 -37
  29. package/lib/lib/load-config.js +42 -0
  30. package/lib/lib/normalizeTailwindDirectives.js +5 -3
  31. package/lib/lib/offsets.js +93 -4
  32. package/lib/lib/partitionApplyAtRules.js +3 -1
  33. package/lib/lib/regex.js +21 -7
  34. package/lib/lib/remap-bitfield.js +89 -0
  35. package/lib/lib/resolveDefaultsAtRules.js +28 -24
  36. package/lib/lib/setupContextUtils.js +190 -131
  37. package/lib/lib/setupTrackingContext.js +49 -26
  38. package/lib/lib/sharedState.js +37 -10
  39. package/lib/lib/substituteScreenAtRules.js +5 -3
  40. package/lib/oxide/cli/build/deps.js +89 -0
  41. package/lib/oxide/cli/build/index.js +53 -0
  42. package/lib/oxide/cli/build/plugin.js +375 -0
  43. package/lib/oxide/cli/build/utils.js +87 -0
  44. package/lib/oxide/cli/build/watching.js +179 -0
  45. package/lib/oxide/cli/help/index.js +72 -0
  46. package/lib/oxide/cli/index.js +214 -0
  47. package/lib/oxide/cli/init/index.js +52 -0
  48. package/lib/oxide/cli.js +5 -0
  49. package/lib/oxide/postcss-plugin.js +2 -0
  50. package/lib/plugin.js +98 -0
  51. package/lib/postcss-plugins/nesting/index.js +3 -1
  52. package/lib/postcss-plugins/nesting/plugin.js +10 -8
  53. package/lib/processTailwindFeatures.js +14 -12
  54. package/lib/public/colors.js +49 -25
  55. package/lib/public/create-plugin.js +5 -3
  56. package/lib/public/default-config.js +6 -4
  57. package/lib/public/default-theme.js +6 -4
  58. package/lib/public/load-config.js +12 -0
  59. package/lib/public/resolve-config.js +6 -4
  60. package/lib/util/applyImportantSelector.js +36 -0
  61. package/lib/util/bigSign.js +3 -1
  62. package/lib/util/buildMediaQuery.js +3 -1
  63. package/lib/util/cloneDeep.js +3 -1
  64. package/lib/util/cloneNodes.js +5 -3
  65. package/lib/util/color.js +30 -12
  66. package/lib/util/colorNames.js +752 -0
  67. package/lib/util/configurePlugins.js +3 -1
  68. package/lib/util/createPlugin.js +3 -1
  69. package/lib/util/createUtilityPlugin.js +7 -5
  70. package/lib/util/dataTypes.js +74 -17
  71. package/lib/util/defaults.js +9 -7
  72. package/lib/util/escapeClassName.js +10 -8
  73. package/lib/util/escapeCommas.js +3 -1
  74. package/lib/util/flattenColorPalette.js +3 -1
  75. package/lib/util/formatVariantSelector.js +138 -160
  76. package/lib/util/getAllConfigs.js +8 -6
  77. package/lib/util/hashConfig.js +6 -4
  78. package/lib/util/isKeyframeRule.js +3 -1
  79. package/lib/util/isPlainObject.js +3 -1
  80. package/lib/util/isSyntacticallyValidPropertyValue.js +3 -1
  81. package/lib/util/log.js +8 -4
  82. package/lib/util/nameClass.js +12 -6
  83. package/lib/util/negateValue.js +4 -2
  84. package/lib/util/normalizeConfig.js +28 -27
  85. package/lib/util/normalizeScreens.js +12 -4
  86. package/lib/util/parseAnimationValue.js +3 -1
  87. package/lib/util/parseBoxShadowValue.js +6 -2
  88. package/lib/util/parseDependency.js +3 -1
  89. package/lib/util/parseGlob.js +6 -4
  90. package/lib/util/parseObjectStyles.js +9 -7
  91. package/lib/util/pluginUtils.js +54 -54
  92. package/lib/util/prefixSelector.js +27 -11
  93. package/lib/util/pseudoElements.js +229 -0
  94. package/lib/util/removeAlphaVariables.js +3 -1
  95. package/lib/util/resolveConfig.js +17 -15
  96. package/lib/util/resolveConfigPath.js +26 -10
  97. package/lib/util/responsive.js +6 -4
  98. package/lib/util/splitAtTopLevelOnly.js +10 -2
  99. package/lib/util/tap.js +3 -1
  100. package/lib/util/toColorValue.js +3 -1
  101. package/lib/util/toPath.js +3 -1
  102. package/lib/util/transformThemeValue.js +6 -4
  103. package/lib/util/validateConfig.js +16 -3
  104. package/lib/util/validateFormalSyntax.js +3 -1
  105. package/lib/util/withAlphaVariable.js +6 -2
  106. package/loadConfig.d.ts +4 -0
  107. package/loadConfig.js +2 -0
  108. package/package.json +45 -37
  109. package/peers/index.js +63324 -46417
  110. package/resolveConfig.d.ts +11 -2
  111. package/scripts/swap-engines.js +40 -0
  112. package/src/cli/build/index.js +13 -9
  113. package/src/cli/build/plugin.js +42 -32
  114. package/src/cli/build/watching.js +4 -2
  115. package/src/cli/index.js +216 -3
  116. package/src/cli/init/index.js +37 -8
  117. package/src/cli.js +4 -220
  118. package/src/corePluginList.js +1 -1
  119. package/src/corePlugins.js +207 -32
  120. package/src/css/preflight.css +2 -0
  121. package/src/featureFlags.js +6 -0
  122. package/src/index.js +1 -47
  123. package/src/lib/content.js +12 -17
  124. package/src/lib/defaultExtractor.js +9 -3
  125. package/src/lib/detectNesting.js +9 -1
  126. package/src/lib/expandApplyAtRules.js +8 -1
  127. package/src/lib/expandTailwindAtRules.js +36 -7
  128. package/src/lib/generateRules.js +124 -42
  129. package/src/lib/getModuleDependencies.js +70 -30
  130. package/src/lib/load-config.ts +31 -0
  131. package/src/lib/offsets.js +104 -1
  132. package/src/lib/remap-bitfield.js +82 -0
  133. package/src/lib/setupContextUtils.js +103 -54
  134. package/src/lib/setupTrackingContext.js +36 -11
  135. package/src/lib/sharedState.js +15 -4
  136. package/src/oxide/cli/build/deps.ts +91 -0
  137. package/src/oxide/cli/build/index.ts +47 -0
  138. package/src/oxide/cli/build/plugin.ts +442 -0
  139. package/src/oxide/cli/build/utils.ts +74 -0
  140. package/src/oxide/cli/build/watching.ts +225 -0
  141. package/src/oxide/cli/help/index.ts +69 -0
  142. package/src/oxide/cli/index.ts +204 -0
  143. package/src/oxide/cli/init/index.ts +59 -0
  144. package/src/oxide/cli.ts +1 -0
  145. package/src/oxide/postcss-plugin.ts +1 -0
  146. package/src/plugin.js +107 -0
  147. package/src/public/colors.js +22 -0
  148. package/src/public/default-config.js +1 -1
  149. package/src/public/default-theme.js +2 -2
  150. package/src/public/load-config.js +2 -0
  151. package/src/util/applyImportantSelector.js +27 -0
  152. package/src/util/color.js +18 -3
  153. package/src/util/colorNames.js +150 -0
  154. package/src/util/dataTypes.js +33 -4
  155. package/src/util/formatVariantSelector.js +160 -163
  156. package/src/util/getAllConfigs.js +2 -2
  157. package/src/util/negateValue.js +1 -1
  158. package/src/util/normalizeConfig.js +2 -1
  159. package/src/util/pluginUtils.js +16 -31
  160. package/src/util/prefixSelector.js +28 -10
  161. package/src/util/pseudoElements.js +170 -0
  162. package/src/util/resolveConfigPath.js +12 -1
  163. package/src/util/splitAtTopLevelOnly.js +8 -1
  164. package/src/util/validateConfig.js +13 -0
  165. package/stubs/.gitignore +1 -0
  166. package/stubs/.prettierrc.json +6 -0
  167. package/stubs/{defaultConfig.stub.js → config.full.js} +183 -146
  168. package/stubs/{simpleConfig.stub.js → config.simple.js} +0 -1
  169. package/stubs/postcss.config.js +6 -0
  170. package/stubs/tailwind.config.cjs +2 -0
  171. package/stubs/tailwind.config.js +2 -0
  172. package/stubs/tailwind.config.ts +3 -0
  173. package/types/config.d.ts +15 -9
  174. package/types/generated/colors.d.ts +22 -0
  175. package/types/generated/corePluginList.d.ts +1 -1
  176. package/types/generated/default-theme.d.ts +108 -79
  177. package/CHANGELOG.md +0 -2336
  178. package/lib/cli/shared.js +0 -12
  179. package/lib/constants.js +0 -44
  180. package/scripts/install-integrations.js +0 -27
  181. package/scripts/rebuildFixtures.js +0 -68
  182. package/src/cli/shared.js +0 -5
  183. package/src/constants.js +0 -17
  184. /package/stubs/{defaultPostCssConfig.stub.js → postcss.config.cjs} +0 -0
@@ -2,31 +2,59 @@ import selectorParser from 'postcss-selector-parser'
2
2
  import unescape from 'postcss-selector-parser/dist/util/unesc'
3
3
  import escapeClassName from '../util/escapeClassName'
4
4
  import prefixSelector from '../util/prefixSelector'
5
+ import { movePseudos } from './pseudoElements'
6
+
7
+ /** @typedef {import('postcss-selector-parser').Root} Root */
8
+ /** @typedef {import('postcss-selector-parser').Selector} Selector */
9
+ /** @typedef {import('postcss-selector-parser').Pseudo} Pseudo */
10
+ /** @typedef {import('postcss-selector-parser').Node} Node */
11
+
12
+ /** @typedef {{format: string, isArbitraryVariant: boolean}[]} RawFormats */
13
+ /** @typedef {import('postcss-selector-parser').Root} ParsedFormats */
14
+ /** @typedef {RawFormats | ParsedFormats} AcceptedFormats */
5
15
 
6
16
  let MERGE = ':merge'
7
- let PARENT = '&'
8
-
9
- export let selectorFunctions = new Set([MERGE])
10
-
11
- export function formatVariantSelector(current, ...others) {
12
- for (let other of others) {
13
- let incomingValue = resolveFunctionArgument(other, MERGE)
14
- if (incomingValue !== null) {
15
- let existingValue = resolveFunctionArgument(current, MERGE, incomingValue)
16
- if (existingValue !== null) {
17
- let existingTarget = `${MERGE}(${incomingValue})`
18
- let splitIdx = other.indexOf(existingTarget)
19
- let addition = other.slice(splitIdx + existingTarget.length).split(' ')[0]
20
-
21
- current = current.replace(existingTarget, existingTarget + addition)
22
- continue
23
- }
17
+
18
+ /**
19
+ * @param {RawFormats} formats
20
+ * @param {{context: any, candidate: string, base: string | null}} options
21
+ * @returns {ParsedFormats | null}
22
+ */
23
+ export function formatVariantSelector(formats, { context, candidate }) {
24
+ let prefix = context?.tailwindConfig.prefix ?? ''
25
+
26
+ // Parse the format selector into an AST
27
+ let parsedFormats = formats.map((format) => {
28
+ let ast = selectorParser().astSync(format.format)
29
+
30
+ return {
31
+ ...format,
32
+ ast: format.isArbitraryVariant ? ast : prefixSelector(prefix, ast),
24
33
  }
34
+ })
35
+
36
+ // We start with the candidate selector
37
+ let formatAst = selectorParser.root({
38
+ nodes: [
39
+ selectorParser.selector({
40
+ nodes: [selectorParser.className({ value: escapeClassName(candidate) })],
41
+ }),
42
+ ],
43
+ })
25
44
 
26
- current = other.replace(PARENT, current)
45
+ // And iteratively merge each format selector into the candidate selector
46
+ for (let { ast } of parsedFormats) {
47
+ // 1. Handle :merge() special pseudo-class
48
+ ;[formatAst, ast] = handleMergePseudo(formatAst, ast)
49
+
50
+ // 2. Merge the format selector into the current selector AST
51
+ ast.walkNesting((nesting) => nesting.replaceWith(...formatAst.nodes[0].nodes))
52
+
53
+ // 3. Keep going!
54
+ formatAst = ast
27
55
  }
28
56
 
29
- return current
57
+ return formatAst
30
58
  }
31
59
 
32
60
  /**
@@ -35,11 +63,11 @@ export function formatVariantSelector(current, ...others) {
35
63
  * Technically :is(), :not(), :has(), etc… can have combinators but those are nested
36
64
  * inside the relevant node and won't be picked up so they're fine to ignore
37
65
  *
38
- * @param {import('postcss-selector-parser').Node} node
39
- * @returns {import('postcss-selector-parser').Node[]}
66
+ * @param {Node} node
67
+ * @returns {Node[]}
40
68
  **/
41
69
  function simpleSelectorForNode(node) {
42
- /** @type {import('postcss-selector-parser').Node[]} */
70
+ /** @type {Node[]} */
43
71
  let nodes = []
44
72
 
45
73
  // Walk backwards until we hit a combinator node (or the start)
@@ -60,8 +88,8 @@ function simpleSelectorForNode(node) {
60
88
  * Resorts the nodes in a selector to ensure they're in the correct order
61
89
  * Tags go before classes, and pseudo classes go after classes
62
90
  *
63
- * @param {import('postcss-selector-parser').Selector} sel
64
- * @returns {import('postcss-selector-parser').Selector}
91
+ * @param {Selector} sel
92
+ * @returns {Selector}
65
93
  **/
66
94
  function resortSelector(sel) {
67
95
  sel.sort((a, b) => {
@@ -81,7 +109,19 @@ function resortSelector(sel) {
81
109
  return sel
82
110
  }
83
111
 
84
- function eliminateIrrelevantSelectors(sel, base) {
112
+ /**
113
+ * Remove extraneous selectors that do not include the base class/candidate
114
+ *
115
+ * Example:
116
+ * Given the utility `.a, .b { color: red}`
117
+ * Given the candidate `sm:b`
118
+ *
119
+ * The final selector should be `.sm\:b` and not `.a, .sm\:b`
120
+ *
121
+ * @param {Selector} ast
122
+ * @param {string} base
123
+ */
124
+ export function eliminateIrrelevantSelectors(sel, base) {
85
125
  let hasClassesMatchingCandidate = false
86
126
 
87
127
  sel.walk((child) => {
@@ -104,41 +144,26 @@ function eliminateIrrelevantSelectors(sel, base) {
104
144
  // TODO: Can we do this for :matches, :is, and :where?
105
145
  }
106
146
 
107
- export function finalizeSelector(
108
- format,
109
- {
110
- selector,
111
- candidate,
112
- context,
113
- isArbitraryVariant,
114
-
115
- // Split by the separator, but ignore the separator inside square brackets:
116
- //
117
- // E.g.: dark:lg:hover:[paint-order:markers]
118
- // ┬ ┬ ┬ ┬
119
- // ╰── We will not split here
120
- // ╰──┴─────┴─────────────── We will split here
121
- //
122
- base = candidate
123
- .split(new RegExp(`\\${context?.tailwindConfig?.separator ?? ':'}(?![^[]*\\])`))
124
- .pop(),
125
- }
126
- ) {
127
- let ast = selectorParser().astSync(selector)
128
-
129
- // We explicitly DO NOT prefix classes in arbitrary variants
130
- if (context?.tailwindConfig?.prefix && !isArbitraryVariant) {
131
- format = prefixSelector(context.tailwindConfig.prefix, format)
132
- }
133
-
134
- format = format.replace(PARENT, `.${escapeClassName(candidate)}`)
135
-
136
- let formatAst = selectorParser().astSync(format)
147
+ /**
148
+ * @param {string} current
149
+ * @param {AcceptedFormats} formats
150
+ * @param {{context: any, candidate: string, base: string | null}} options
151
+ * @returns {string}
152
+ */
153
+ export function finalizeSelector(current, formats, { context, candidate, base }) {
154
+ let separator = context?.tailwindConfig?.separator ?? ':'
155
+
156
+ // Split by the separator, but ignore the separator inside square brackets:
157
+ //
158
+ // E.g.: dark:lg:hover:[paint-order:markers]
159
+ //
160
+ // │ │ │ ╰── We will not split here
161
+ // ╰──┴─────┴─────────────── We will split here
162
+ //
163
+ base = base ?? candidate.split(new RegExp(`\\${separator}(?![^[]*\\])`)).pop()
137
164
 
138
- // Remove extraneous selectors that do not include the base class/candidate being matched against
139
- // For example if we have a utility defined `.a, .b { color: red}`
140
- // And the formatted variant is sm:b then we want the final selector to be `.sm\:b` and not `.a, .sm\:b`
141
- ast.each((sel) => eliminateIrrelevantSelectors(sel, base))
165
+ // Parse the selector into an AST
166
+ let selector = selectorParser().astSync(current)
142
167
 
143
168
  // Normalize escaped classes, e.g.:
144
169
  //
@@ -151,18 +176,31 @@ export function finalizeSelector(
151
176
  // base in selector: bg-\\[rgb\\(255\\,0\\,0\\)\\]
152
177
  // escaped base: bg-\\[rgb\\(255\\2c 0\\2c 0\\)\\]
153
178
  //
154
- ast.walkClasses((node) => {
179
+ selector.walkClasses((node) => {
155
180
  if (node.raws && node.value.includes(base)) {
156
181
  node.raws.value = escapeClassName(unescape(node.raws.value))
157
182
  }
158
183
  })
159
184
 
185
+ // Remove extraneous selectors that do not include the base candidate
186
+ selector.each((sel) => eliminateIrrelevantSelectors(sel, base))
187
+
188
+ // If there are no formats that means there were no variants added to the candidate
189
+ // so we can just return the selector as-is
190
+ let formatAst = Array.isArray(formats)
191
+ ? formatVariantSelector(formats, { context, candidate })
192
+ : formats
193
+
194
+ if (formatAst === null) {
195
+ return selector.toString()
196
+ }
197
+
160
198
  let simpleStart = selectorParser.comment({ value: '/*__simple__*/' })
161
199
  let simpleEnd = selectorParser.comment({ value: '/*__simple__*/' })
162
200
 
163
201
  // We can safely replace the escaped base now, since the `base` section is
164
202
  // now in a normalized escaped value.
165
- ast.walkClasses((node) => {
203
+ selector.walkClasses((node) => {
166
204
  if (node.value !== base) {
167
205
  return
168
206
  }
@@ -181,7 +219,7 @@ export function finalizeSelector(
181
219
  parent.insertAfter(simpleSelector[simpleSelector.length - 1], simpleEnd)
182
220
 
183
221
  for (let child of formatNodes) {
184
- parent.insertBefore(simpleSelector[0], child)
222
+ parent.insertBefore(simpleSelector[0], child.clone())
185
223
  }
186
224
 
187
225
  node.remove()
@@ -200,120 +238,79 @@ export function finalizeSelector(
200
238
  simpleEnd.remove()
201
239
  })
202
240
 
203
- // This will make sure to move pseudo's to the correct spot (the end for
204
- // pseudo elements) because otherwise the selector will never work
205
- // anyway.
206
- //
207
- // E.g.:
208
- // - `before:hover:text-center` would result in `.before\:hover\:text-center:hover::before`
209
- // - `hover:before:text-center` would result in `.hover\:before\:text-center:hover::before`
210
- //
211
- // `::before:hover` doesn't work, which means that we can make it work for you by flipping the order.
212
- function collectPseudoElements(selector) {
213
- let nodes = []
214
-
215
- for (let node of selector.nodes) {
216
- if (isPseudoElement(node)) {
217
- nodes.push(node)
218
- selector.removeChild(node)
219
- }
220
-
221
- if (node?.nodes) {
222
- nodes.push(...collectPseudoElements(node))
223
- }
241
+ // Remove unnecessary pseudo selectors that we used as placeholders
242
+ selector.walkPseudos((p) => {
243
+ if (p.value === MERGE) {
244
+ p.replaceWith(p.nodes)
224
245
  }
246
+ })
225
247
 
226
- return nodes
227
- }
248
+ // Move pseudo elements to the end of the selector (if necessary)
249
+ selector.each((sel) => movePseudos(sel))
228
250
 
229
- // Remove unnecessary pseudo selectors that we used as placeholders
230
- ast.each((selector) => {
231
- selector.walkPseudos((p) => {
232
- if (selectorFunctions.has(p.value)) {
233
- p.replaceWith(p.nodes)
234
- }
235
- })
236
-
237
- let pseudoElements = collectPseudoElements(selector)
238
- if (pseudoElements.length > 0) {
239
- selector.nodes.push(pseudoElements.sort(sortSelector))
251
+ return selector.toString()
252
+ }
253
+
254
+ /**
255
+ *
256
+ * @param {Selector} selector
257
+ * @param {Selector} format
258
+ */
259
+ export function handleMergePseudo(selector, format) {
260
+ /** @type {{pseudo: Pseudo, value: string}[]} */
261
+ let merges = []
262
+
263
+ // Find all :merge() pseudo-classes in `selector`
264
+ selector.walkPseudos((pseudo) => {
265
+ if (pseudo.value === MERGE) {
266
+ merges.push({
267
+ pseudo,
268
+ value: pseudo.nodes[0].toString(),
269
+ })
240
270
  }
241
271
  })
242
272
 
243
- return ast.toString()
244
- }
273
+ // Find all :merge() "attachments" in `format` and attach them to the matching selector in `selector`
274
+ format.walkPseudos((pseudo) => {
275
+ if (pseudo.value !== MERGE) {
276
+ return
277
+ }
245
278
 
246
- // Note: As a rule, double colons (::) should be used instead of a single colon
247
- // (:). This distinguishes pseudo-classes from pseudo-elements. However, since
248
- // this distinction was not present in older versions of the W3C spec, most
249
- // browsers support both syntaxes for the original pseudo-elements.
250
- let pseudoElementsBC = [':before', ':after', ':first-line', ':first-letter']
251
-
252
- // These pseudo-elements _can_ be combined with other pseudo selectors AND the order does matter.
253
- let pseudoElementExceptions = ['::file-selector-button']
254
-
255
- // This will make sure to move pseudo's to the correct spot (the end for
256
- // pseudo elements) because otherwise the selector will never work
257
- // anyway.
258
- //
259
- // E.g.:
260
- // - `before:hover:text-center` would result in `.before\:hover\:text-center:hover::before`
261
- // - `hover:before:text-center` would result in `.hover\:before\:text-center:hover::before`
262
- //
263
- // `::before:hover` doesn't work, which means that we can make it work
264
- // for you by flipping the order.
265
- function sortSelector(a, z) {
266
- // Both nodes are non-pseudo's so we can safely ignore them and keep
267
- // them in the same order.
268
- if (a.type !== 'pseudo' && z.type !== 'pseudo') {
269
- return 0
270
- }
279
+ let value = pseudo.nodes[0].toString()
271
280
 
272
- // If one of them is a combinator, we need to keep it in the same order
273
- // because that means it will start a new "section" in the selector.
274
- if ((a.type === 'combinator') ^ (z.type === 'combinator')) {
275
- return 0
276
- }
281
+ // Does `selector` contain a :merge() pseudo-class with the same value?
282
+ let existing = merges.find((merge) => merge.value === value)
277
283
 
278
- // One of the items is a pseudo and the other one isn't. Let's move
279
- // the pseudo to the right.
280
- if ((a.type === 'pseudo') ^ (z.type === 'pseudo')) {
281
- return (a.type === 'pseudo') - (z.type === 'pseudo')
282
- }
284
+ // Nope so there's nothing to do
285
+ if (!existing) {
286
+ return
287
+ }
283
288
 
284
- // Both are pseudo's, move the pseudo elements (except for
285
- // ::file-selector-button) to the right.
286
- return isPseudoElement(a) - isPseudoElement(z)
287
- }
289
+ // Everything after `:merge()` up to the next combinator is what is attached to the merged selector
290
+ let attachments = []
291
+ let next = pseudo.next()
292
+ while (next && next.type !== 'combinator') {
293
+ attachments.push(next)
294
+ next = next.next()
295
+ }
288
296
 
289
- function isPseudoElement(node) {
290
- if (node.type !== 'pseudo') return false
291
- if (pseudoElementExceptions.includes(node.value)) return false
297
+ let combinator = next
292
298
 
293
- return node.value.startsWith('::') || pseudoElementsBC.includes(node.value)
294
- }
299
+ existing.pseudo.parent.insertAfter(
300
+ existing.pseudo,
301
+ selectorParser.selector({ nodes: attachments.map((node) => node.clone()) })
302
+ )
295
303
 
296
- function resolveFunctionArgument(haystack, needle, arg) {
297
- let startIdx = haystack.indexOf(arg ? `${needle}(${arg})` : needle)
298
- if (startIdx === -1) return null
299
-
300
- // Start inside the `(`
301
- startIdx += needle.length + 1
302
-
303
- let target = ''
304
- let count = 0
305
-
306
- for (let char of haystack.slice(startIdx)) {
307
- if (char !== '(' && char !== ')') {
308
- target += char
309
- } else if (char === '(') {
310
- target += char
311
- count++
312
- } else if (char === ')') {
313
- if (--count < 0) break // unbalanced
314
- target += char
304
+ pseudo.remove()
305
+ attachments.forEach((node) => node.remove())
306
+
307
+ // What about this case:
308
+ // :merge(.group):focus > &
309
+ // :merge(.group):hover &
310
+ if (combinator && combinator.type === 'combinator') {
311
+ combinator.remove()
315
312
  }
316
- }
313
+ })
317
314
 
318
- return target
315
+ return [selector, format]
319
316
  }
@@ -1,8 +1,8 @@
1
- import defaultConfig from '../../stubs/defaultConfig.stub.js'
1
+ import defaultFullConfig from '../../stubs/config.full.js'
2
2
  import { flagEnabled } from '../featureFlags'
3
3
 
4
4
  export default function getAllConfigs(config) {
5
- const configs = (config?.presets ?? [defaultConfig])
5
+ const configs = (config?.presets ?? [defaultFullConfig])
6
6
  .slice()
7
7
  .reverse()
8
8
  .flatMap((preset) => getAllConfigs(preset instanceof Function ? preset() : preset))
@@ -1,4 +1,4 @@
1
- export default function (value) {
1
+ export default function negateValue(value) {
2
2
  value = `${value}`
3
3
 
4
4
  if (value === '0') {
@@ -1,3 +1,4 @@
1
+ import { flagEnabled } from '../featureFlags'
1
2
  import log, { dim } from './log'
2
3
 
3
4
  export function normalizeConfig(config) {
@@ -189,7 +190,7 @@ export function normalizeConfig(config) {
189
190
  return content.relative
190
191
  }
191
192
 
192
- return config.future?.relativeContentPathsByDefault ?? false
193
+ return flagEnabled(config, 'relativeContentPathsByDefault')
193
194
  })(),
194
195
 
195
196
  files: (() => {
@@ -1,4 +1,3 @@
1
- import selectorParser from 'postcss-selector-parser'
2
1
  import escapeCommas from './escapeCommas'
3
2
  import { withAlphaValue } from './withAlphaVariable'
4
3
  import {
@@ -21,37 +20,19 @@ import negateValue from './negateValue'
21
20
  import { backgroundSize } from './validateFormalSyntax'
22
21
  import { flagEnabled } from '../featureFlags.js'
23
22
 
23
+ /**
24
+ * @param {import('postcss-selector-parser').Container} selectors
25
+ * @param {(className: string) => string} updateClass
26
+ * @returns {string}
27
+ */
24
28
  export function updateAllClasses(selectors, updateClass) {
25
- let parser = selectorParser((selectors) => {
26
- selectors.walkClasses((sel) => {
27
- let updatedClass = updateClass(sel.value)
28
- sel.value = updatedClass
29
- if (sel.raws && sel.raws.value) {
30
- sel.raws.value = escapeCommas(sel.raws.value)
31
- }
32
- })
33
- })
34
-
35
- let result = parser.processSync(selectors)
36
-
37
- return result
38
- }
29
+ selectors.walkClasses((sel) => {
30
+ sel.value = updateClass(sel.value)
39
31
 
40
- export function filterSelectorsForClass(selectors, classCandidate) {
41
- let parser = selectorParser((selectors) => {
42
- selectors.each((sel) => {
43
- const containsClass = sel.nodes.some(
44
- (node) => node.type === 'class' && node.value === classCandidate
45
- )
46
- if (!containsClass) {
47
- sel.remove()
48
- }
49
- })
32
+ if (sel.raws && sel.raws.value) {
33
+ sel.raws.value = escapeCommas(sel.raws.value)
34
+ }
50
35
  })
51
-
52
- let result = parser.processSync(selectors)
53
-
54
- return result
55
36
  }
56
37
 
57
38
  function resolveArbitraryValue(modifier, validate) {
@@ -133,6 +114,10 @@ export function parseColorFormat(value) {
133
114
  return value
134
115
  }
135
116
 
117
+ function unwrapArbitraryModifier(modifier) {
118
+ return normalize(modifier.slice(1, -1))
119
+ }
120
+
136
121
  export function asColor(modifier, options = {}, { tailwindConfig = {} } = {}) {
137
122
  if (options.values?.[modifier] !== undefined) {
138
123
  return parseColorFormat(options.values?.[modifier])
@@ -153,7 +138,7 @@ export function asColor(modifier, options = {}, { tailwindConfig = {} } = {}) {
153
138
  normalizedColor = parseColorFormat(normalizedColor)
154
139
 
155
140
  if (isArbitraryValue(alpha)) {
156
- return withAlphaValue(normalizedColor, alpha.slice(1, -1))
141
+ return withAlphaValue(normalizedColor, unwrapArbitraryModifier(alpha))
157
142
  }
158
143
 
159
144
  if (tailwindConfig.theme?.opacity?.[alpha] === undefined) {
@@ -287,7 +272,7 @@ export function* getMatchingTypes(types, rawModifier, options, tailwindConfig) {
287
272
  if (configValue !== null) {
288
273
  utilityModifier = configValue
289
274
  } else if (isArbitraryValue(utilityModifier)) {
290
- utilityModifier = utilityModifier.slice(1, -1)
275
+ utilityModifier = unwrapArbitraryModifier(utilityModifier)
291
276
  }
292
277
  }
293
278
  }
@@ -1,14 +1,32 @@
1
1
  import parser from 'postcss-selector-parser'
2
2
 
3
+ /**
4
+ * @template {string | import('postcss-selector-parser').Root} T
5
+ *
6
+ * Prefix all classes in the selector with the given prefix
7
+ *
8
+ * It can take either a string or a selector AST and will return the same type
9
+ *
10
+ * @param {string} prefix
11
+ * @param {T} selector
12
+ * @param {boolean} prependNegative
13
+ * @returns {T}
14
+ */
3
15
  export default function (prefix, selector, prependNegative = false) {
4
- return parser((selectors) => {
5
- selectors.walkClasses((classSelector) => {
6
- let baseClass = classSelector.value
7
- let shouldPlaceNegativeBeforePrefix = prependNegative && baseClass.startsWith('-')
8
-
9
- classSelector.value = shouldPlaceNegativeBeforePrefix
10
- ? `-${prefix}${baseClass.slice(1)}`
11
- : `${prefix}${baseClass}`
12
- })
13
- }).processSync(selector)
16
+ if (prefix === '') {
17
+ return selector
18
+ }
19
+
20
+ let ast = typeof selector === 'string' ? parser().astSync(selector) : selector
21
+
22
+ ast.walkClasses((classSelector) => {
23
+ let baseClass = classSelector.value
24
+ let shouldPlaceNegativeBeforePrefix = prependNegative && baseClass.startsWith('-')
25
+
26
+ classSelector.value = shouldPlaceNegativeBeforePrefix
27
+ ? `-${prefix}${baseClass.slice(1)}`
28
+ : `${prefix}${baseClass}`
29
+ })
30
+
31
+ return typeof selector === 'string' ? ast.toString() : ast
14
32
  }