tailwindcss 0.0.0-insiders.f4635e0 → 0.0.0-insiders.f4a2ccd

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 (238) hide show
  1. package/LICENSE +1 -2
  2. package/README.md +15 -7
  3. package/colors.d.ts +3 -0
  4. package/colors.js +2 -304
  5. package/defaultConfig.d.ts +3 -0
  6. package/defaultConfig.js +2 -4
  7. package/defaultTheme.d.ts +4 -0
  8. package/defaultTheme.js +2 -4
  9. package/lib/cli/build/deps.js +54 -0
  10. package/lib/cli/build/index.js +52 -0
  11. package/lib/cli/build/plugin.js +376 -0
  12. package/lib/cli/build/utils.js +78 -0
  13. package/lib/cli/build/watching.js +180 -0
  14. package/lib/cli/help/index.js +71 -0
  15. package/lib/cli/index.js +230 -0
  16. package/lib/cli/init/index.js +61 -0
  17. package/lib/cli-peer-dependencies.js +22 -14
  18. package/lib/cli.js +4 -762
  19. package/lib/corePluginList.js +182 -5
  20. package/lib/corePlugins.js +4109 -2952
  21. package/lib/css/preflight.css +24 -9
  22. package/lib/featureFlags.js +68 -50
  23. package/lib/index.js +5 -30
  24. package/lib/lib/cacheInvalidation.js +90 -0
  25. package/lib/lib/collapseAdjacentRules.js +52 -36
  26. package/lib/lib/collapseDuplicateDeclarations.js +83 -0
  27. package/lib/lib/content.js +177 -0
  28. package/lib/lib/defaultExtractor.js +241 -0
  29. package/lib/lib/detectNesting.js +39 -17
  30. package/lib/lib/evaluateTailwindFunctions.js +203 -161
  31. package/lib/lib/expandApplyAtRules.js +503 -221
  32. package/lib/lib/expandTailwindAtRules.js +255 -243
  33. package/lib/lib/findAtConfigPath.js +44 -0
  34. package/lib/lib/generateRules.js +831 -317
  35. package/lib/lib/getModuleDependencies.js +93 -49
  36. package/lib/lib/load-config.js +36 -0
  37. package/lib/lib/normalizeTailwindDirectives.js +79 -60
  38. package/lib/lib/offsets.js +304 -0
  39. package/lib/lib/partitionApplyAtRules.js +56 -0
  40. package/lib/lib/regex.js +60 -0
  41. package/lib/lib/remap-bitfield.js +87 -0
  42. package/lib/lib/resolveDefaultsAtRules.js +150 -94
  43. package/lib/lib/setupContextUtils.js +1197 -685
  44. package/lib/lib/setupTrackingContext.js +150 -177
  45. package/lib/lib/sharedState.js +70 -21
  46. package/lib/lib/substituteScreenAtRules.js +26 -28
  47. package/lib/oxide/cli/build/deps.js +81 -0
  48. package/lib/oxide/cli/build/index.js +51 -0
  49. package/lib/oxide/cli/build/plugin.js +373 -0
  50. package/lib/oxide/cli/build/utils.js +77 -0
  51. package/lib/oxide/cli/build/watching.js +177 -0
  52. package/lib/oxide/cli/help/index.js +70 -0
  53. package/lib/oxide/cli/index.js +214 -0
  54. package/lib/oxide/cli/init/index.js +50 -0
  55. package/lib/oxide/cli.js +5 -0
  56. package/lib/oxide/postcss-plugin.js +2 -0
  57. package/lib/plugin.js +98 -0
  58. package/{nesting → lib/postcss-plugins/nesting}/README.md +2 -2
  59. package/lib/postcss-plugins/nesting/index.js +19 -0
  60. package/lib/postcss-plugins/nesting/plugin.js +87 -0
  61. package/lib/processTailwindFeatures.js +58 -56
  62. package/lib/public/colors.js +353 -0
  63. package/lib/public/create-plugin.js +15 -0
  64. package/lib/public/default-config.js +16 -0
  65. package/lib/public/default-theme.js +16 -0
  66. package/lib/public/load-config.js +10 -0
  67. package/lib/public/resolve-config.js +22 -0
  68. package/lib/util/applyImportantSelector.js +22 -0
  69. package/lib/util/bigSign.js +7 -6
  70. package/lib/util/buildMediaQuery.js +21 -32
  71. package/lib/util/cloneDeep.js +16 -14
  72. package/lib/util/cloneNodes.js +29 -15
  73. package/lib/util/color.js +104 -66
  74. package/lib/util/configurePlugins.js +17 -15
  75. package/lib/util/createPlugin.js +23 -26
  76. package/lib/util/createUtilityPlugin.js +46 -46
  77. package/lib/util/dataTypes.js +266 -0
  78. package/lib/util/defaults.js +20 -15
  79. package/lib/util/escapeClassName.js +18 -17
  80. package/lib/util/escapeCommas.js +7 -6
  81. package/lib/util/flattenColorPalette.js +13 -12
  82. package/lib/util/formatVariantSelector.js +341 -0
  83. package/lib/util/getAllConfigs.js +44 -18
  84. package/lib/util/hashConfig.js +15 -12
  85. package/lib/util/isKeyframeRule.js +7 -6
  86. package/lib/util/isPlainObject.js +11 -11
  87. package/lib/util/isSyntacticallyValidPropertyValue.js +72 -0
  88. package/lib/util/log.js +52 -33
  89. package/lib/util/nameClass.js +35 -29
  90. package/lib/util/negateValue.js +31 -17
  91. package/lib/util/normalizeConfig.js +294 -0
  92. package/lib/util/normalizeScreens.js +170 -0
  93. package/lib/util/parseAnimationValue.js +85 -54
  94. package/lib/util/parseBoxShadowValue.js +84 -0
  95. package/lib/util/parseDependency.js +41 -70
  96. package/lib/util/parseGlob.js +34 -0
  97. package/lib/util/parseObjectStyles.js +30 -24
  98. package/lib/util/pluginUtils.js +234 -288
  99. package/lib/util/prefixSelector.js +33 -19
  100. package/lib/util/removeAlphaVariables.js +29 -0
  101. package/lib/util/resolveConfig.js +221 -256
  102. package/lib/util/resolveConfigPath.js +55 -48
  103. package/lib/util/responsive.js +18 -14
  104. package/lib/util/splitAtTopLevelOnly.js +49 -0
  105. package/lib/util/tap.js +8 -7
  106. package/lib/util/toColorValue.js +7 -6
  107. package/lib/util/toPath.js +27 -8
  108. package/lib/util/transformThemeValue.js +67 -28
  109. package/lib/util/validateConfig.js +24 -0
  110. package/lib/util/validateFormalSyntax.js +24 -0
  111. package/lib/util/withAlphaVariable.js +67 -57
  112. package/loadConfig.d.ts +4 -0
  113. package/loadConfig.js +2 -0
  114. package/nesting/index.js +2 -12
  115. package/package.json +69 -66
  116. package/peers/index.js +76328 -84169
  117. package/plugin.d.ts +11 -0
  118. package/plugin.js +1 -2
  119. package/resolveConfig.d.ts +12 -0
  120. package/resolveConfig.js +2 -7
  121. package/scripts/create-plugin-list.js +2 -2
  122. package/scripts/generate-types.js +105 -0
  123. package/scripts/release-channel.js +18 -0
  124. package/scripts/release-notes.js +21 -0
  125. package/scripts/swap-engines.js +40 -0
  126. package/scripts/type-utils.js +27 -0
  127. package/src/cli/build/deps.js +56 -0
  128. package/src/cli/build/index.js +49 -0
  129. package/src/cli/build/plugin.js +444 -0
  130. package/src/cli/build/utils.js +76 -0
  131. package/src/cli/build/watching.js +229 -0
  132. package/src/cli/help/index.js +70 -0
  133. package/src/cli/index.js +216 -0
  134. package/src/cli/init/index.js +79 -0
  135. package/src/cli-peer-dependencies.js +7 -1
  136. package/src/cli.js +4 -756
  137. package/src/corePluginList.js +1 -1
  138. package/src/corePlugins.js +2526 -1956
  139. package/src/css/preflight.css +24 -9
  140. package/src/featureFlags.js +33 -10
  141. package/src/index.js +4 -33
  142. package/src/lib/cacheInvalidation.js +52 -0
  143. package/src/lib/collapseAdjacentRules.js +21 -2
  144. package/src/lib/collapseDuplicateDeclarations.js +93 -0
  145. package/src/lib/content.js +208 -0
  146. package/src/lib/defaultExtractor.js +217 -0
  147. package/src/lib/detectNesting.js +30 -3
  148. package/src/lib/evaluateTailwindFunctions.js +84 -10
  149. package/src/lib/expandApplyAtRules.js +509 -153
  150. package/src/lib/expandTailwindAtRules.js +125 -104
  151. package/src/lib/findAtConfigPath.js +48 -0
  152. package/src/lib/generateRules.js +678 -70
  153. package/src/lib/getModuleDependencies.js +70 -30
  154. package/src/lib/load-config.ts +27 -0
  155. package/src/lib/normalizeTailwindDirectives.js +10 -3
  156. package/src/lib/offsets.js +373 -0
  157. package/src/lib/partitionApplyAtRules.js +52 -0
  158. package/src/lib/regex.js +74 -0
  159. package/src/lib/remap-bitfield.js +82 -0
  160. package/src/lib/resolveDefaultsAtRules.js +105 -47
  161. package/src/lib/setupContextUtils.js +848 -247
  162. package/src/lib/setupTrackingContext.js +53 -64
  163. package/src/lib/sharedState.js +60 -6
  164. package/src/lib/substituteScreenAtRules.js +6 -3
  165. package/src/oxide/cli/build/deps.ts +91 -0
  166. package/src/oxide/cli/build/index.ts +47 -0
  167. package/src/oxide/cli/build/plugin.ts +442 -0
  168. package/src/oxide/cli/build/utils.ts +74 -0
  169. package/src/oxide/cli/build/watching.ts +225 -0
  170. package/src/oxide/cli/help/index.ts +69 -0
  171. package/src/oxide/cli/index.ts +204 -0
  172. package/src/oxide/cli/init/index.ts +59 -0
  173. package/src/oxide/cli.ts +1 -0
  174. package/src/oxide/postcss-plugin.ts +1 -0
  175. package/src/plugin.js +107 -0
  176. package/src/postcss-plugins/nesting/README.md +42 -0
  177. package/src/postcss-plugins/nesting/index.js +13 -0
  178. package/src/postcss-plugins/nesting/plugin.js +80 -0
  179. package/src/processTailwindFeatures.js +18 -3
  180. package/src/public/colors.js +322 -0
  181. package/src/public/create-plugin.js +2 -0
  182. package/src/public/default-config.js +4 -0
  183. package/src/public/default-theme.js +4 -0
  184. package/src/public/load-config.js +2 -0
  185. package/src/public/resolve-config.js +7 -0
  186. package/src/util/applyImportantSelector.js +19 -0
  187. package/src/util/buildMediaQuery.js +14 -16
  188. package/src/util/cloneNodes.js +19 -2
  189. package/src/util/color.js +43 -11
  190. package/src/util/createUtilityPlugin.js +2 -11
  191. package/src/util/dataTypes.js +281 -0
  192. package/src/util/defaults.js +6 -0
  193. package/src/util/formatVariantSelector.js +412 -0
  194. package/src/util/getAllConfigs.js +21 -2
  195. package/src/util/isSyntacticallyValidPropertyValue.js +61 -0
  196. package/src/util/log.js +23 -22
  197. package/src/util/nameClass.js +6 -2
  198. package/src/util/negateValue.js +15 -5
  199. package/src/util/normalizeConfig.js +318 -0
  200. package/src/util/normalizeScreens.js +140 -0
  201. package/src/util/parseAnimationValue.js +7 -1
  202. package/src/util/parseBoxShadowValue.js +72 -0
  203. package/src/util/parseDependency.js +37 -38
  204. package/src/util/parseGlob.js +24 -0
  205. package/src/util/pluginUtils.js +219 -219
  206. package/src/util/prefixSelector.js +30 -13
  207. package/src/util/removeAlphaVariables.js +24 -0
  208. package/src/util/resolveConfig.js +86 -91
  209. package/src/util/resolveConfigPath.js +12 -1
  210. package/src/util/splitAtTopLevelOnly.js +52 -0
  211. package/src/util/toPath.js +23 -1
  212. package/src/util/transformThemeValue.js +33 -8
  213. package/src/util/validateConfig.js +13 -0
  214. package/src/util/validateFormalSyntax.js +34 -0
  215. package/src/util/withAlphaVariable.js +14 -9
  216. package/stubs/.gitignore +1 -0
  217. package/stubs/.prettierrc.json +6 -0
  218. package/stubs/{defaultConfig.stub.js → config.full.js} +332 -249
  219. package/stubs/{simpleConfig.stub.js → config.simple.js} +0 -1
  220. package/stubs/postcss.config.js +6 -0
  221. package/stubs/tailwind.config.cjs +2 -0
  222. package/stubs/tailwind.config.js +2 -0
  223. package/stubs/tailwind.config.ts +3 -0
  224. package/types/config.d.ts +373 -0
  225. package/types/generated/.gitkeep +0 -0
  226. package/types/generated/colors.d.ts +298 -0
  227. package/types/generated/corePluginList.d.ts +1 -0
  228. package/types/generated/default-theme.d.ts +347 -0
  229. package/types/index.d.ts +7 -0
  230. package/CHANGELOG.md +0 -1742
  231. package/lib/constants.js +0 -37
  232. package/lib/lib/setupWatchingContext.js +0 -331
  233. package/nesting/plugin.js +0 -41
  234. package/scripts/install-integrations.js +0 -27
  235. package/scripts/rebuildFixtures.js +0 -68
  236. package/src/constants.js +0 -17
  237. package/src/lib/setupWatchingContext.js +0 -307
  238. /package/stubs/{defaultPostCssConfig.stub.js → postcss.config.cjs} +0 -0
@@ -3,13 +3,27 @@ import selectorParser from 'postcss-selector-parser'
3
3
  import parseObjectStyles from '../util/parseObjectStyles'
4
4
  import isPlainObject from '../util/isPlainObject'
5
5
  import prefixSelector from '../util/prefixSelector'
6
- import { updateAllClasses } from '../util/pluginUtils'
6
+ import { updateAllClasses, getMatchingTypes } from '../util/pluginUtils'
7
+ import log from '../util/log'
8
+ import * as sharedState from './sharedState'
9
+ import {
10
+ formatVariantSelector,
11
+ finalizeSelector,
12
+ eliminateIrrelevantSelectors,
13
+ } from '../util/formatVariantSelector'
14
+ import { asClass } from '../util/nameClass'
15
+ import { normalize } from '../util/dataTypes'
16
+ import { isValidVariantFormatString, parseVariant } from './setupContextUtils'
17
+ import isValidArbitraryValue from '../util/isSyntacticallyValidPropertyValue'
18
+ import { splitAtTopLevelOnly } from '../util/splitAtTopLevelOnly.js'
19
+ import { flagEnabled } from '../featureFlags'
20
+ import { applyImportantSelector } from '../util/applyImportantSelector'
7
21
 
8
22
  let classNameParser = selectorParser((selectors) => {
9
23
  return selectors.first.filter(({ type }) => type === 'class').pop().value
10
24
  })
11
25
 
12
- function getClassNameFromSelector(selector) {
26
+ export function getClassNameFromSelector(selector) {
13
27
  return classNameParser.transformSync(selector)
14
28
  }
15
29
 
@@ -20,33 +34,49 @@ function getClassNameFromSelector(selector) {
20
34
  // Example with dynamic classes:
21
35
  // ['grid-cols', '[[linename],1fr,auto]']
22
36
  // ['grid', 'cols-[[linename],1fr,auto]']
23
- function* candidatePermutations(candidate, lastIndex = Infinity) {
24
- if (lastIndex < 0) {
25
- return
26
- }
27
-
28
- let dashIdx
37
+ function* candidatePermutations(candidate) {
38
+ let lastIndex = Infinity
39
+
40
+ while (lastIndex >= 0) {
41
+ let dashIdx
42
+ let wasSlash = false
43
+
44
+ if (lastIndex === Infinity && candidate.endsWith(']')) {
45
+ let bracketIdx = candidate.indexOf('[')
46
+
47
+ // If character before `[` isn't a dash or a slash, this isn't a dynamic class
48
+ // eg. string[]
49
+ if (candidate[bracketIdx - 1] === '-') {
50
+ dashIdx = bracketIdx - 1
51
+ } else if (candidate[bracketIdx - 1] === '/') {
52
+ dashIdx = bracketIdx - 1
53
+ wasSlash = true
54
+ } else {
55
+ dashIdx = -1
56
+ }
57
+ } else if (lastIndex === Infinity && candidate.includes('/')) {
58
+ dashIdx = candidate.lastIndexOf('/')
59
+ wasSlash = true
60
+ } else {
61
+ dashIdx = candidate.lastIndexOf('-', lastIndex)
62
+ }
29
63
 
30
- if (lastIndex === Infinity && candidate.endsWith(']')) {
31
- let bracketIdx = candidate.indexOf('[')
64
+ if (dashIdx < 0) {
65
+ break
66
+ }
32
67
 
33
- // If character before `[` isn't a dash or a slash, this isn't a dynamic class
34
- // eg. string[]
35
- dashIdx = ['-', '/'].includes(candidate[bracketIdx - 1]) ? bracketIdx - 1 : -1
36
- } else {
37
- dashIdx = candidate.lastIndexOf('-', lastIndex)
38
- }
68
+ let prefix = candidate.slice(0, dashIdx)
69
+ let modifier = candidate.slice(wasSlash ? dashIdx : dashIdx + 1)
39
70
 
40
- if (dashIdx < 0) {
41
- return
42
- }
71
+ lastIndex = dashIdx - 1
43
72
 
44
- let prefix = candidate.slice(0, dashIdx)
45
- let modifier = candidate.slice(dashIdx + 1)
46
-
47
- yield [prefix, modifier]
73
+ // TODO: This feels a bit hacky
74
+ if (prefix === '' || modifier === '/') {
75
+ continue
76
+ }
48
77
 
49
- yield* candidatePermutations(candidate, dashIdx - 1)
78
+ yield [prefix, modifier]
79
+ }
50
80
  }
51
81
 
52
82
  function applyPrefix(matches, context) {
@@ -58,9 +88,23 @@ function applyPrefix(matches, context) {
58
88
  let [meta] = match
59
89
  if (meta.options.respectPrefix) {
60
90
  let container = postcss.root({ nodes: [match[1].clone()] })
91
+ let classCandidate = match[1].raws.tailwind.classCandidate
92
+
61
93
  container.walkRules((r) => {
62
- r.selector = prefixSelector(context.tailwindConfig.prefix, r.selector)
94
+ // If this is a negative utility with a dash *before* the prefix we
95
+ // have to ensure that the generated selector matches the candidate
96
+
97
+ // Not doing this will cause `-tw-top-1` to generate the class `.tw--top-1`
98
+ // The disconnect between candidate <-> class can cause @apply to hard crash.
99
+ let shouldPrependNegative = classCandidate.startsWith('-')
100
+
101
+ r.selector = prefixSelector(
102
+ context.tailwindConfig.prefix,
103
+ r.selector,
104
+ shouldPrependNegative
105
+ )
63
106
  })
107
+
64
108
  match[1] = container.nodes[0]
65
109
  }
66
110
  }
@@ -68,20 +112,32 @@ function applyPrefix(matches, context) {
68
112
  return matches
69
113
  }
70
114
 
71
- function applyImportant(matches) {
115
+ function applyImportant(matches, classCandidate) {
72
116
  if (matches.length === 0) {
73
117
  return matches
74
118
  }
119
+
75
120
  let result = []
76
121
 
77
122
  for (let [meta, rule] of matches) {
78
123
  let container = postcss.root({ nodes: [rule.clone()] })
124
+
79
125
  container.walkRules((r) => {
80
- r.selector = updateAllClasses(r.selector, (className) => {
81
- return `!${className}`
82
- })
126
+ let ast = selectorParser().astSync(r.selector)
127
+
128
+ // Remove extraneous selectors that do not include the base candidate
129
+ ast.each((sel) => eliminateIrrelevantSelectors(sel, classCandidate))
130
+
131
+ // Update all instances of the base candidate to include the important marker
132
+ updateAllClasses(ast, (className) =>
133
+ className === classCandidate ? `!${className}` : className
134
+ )
135
+
136
+ r.selector = ast.toString()
137
+
83
138
  r.walkDecls((d) => (d.important = true))
84
139
  })
140
+
85
141
  result.push([{ ...meta, important: true }, container.nodes[0]])
86
142
  }
87
143
 
@@ -102,21 +158,104 @@ function applyVariant(variant, matches, context) {
102
158
  return matches
103
159
  }
104
160
 
161
+ /** @type {{modifier: string | null, value: string | null}} */
162
+ let args = { modifier: null, value: sharedState.NONE }
163
+
164
+ // Retrieve "modifier"
165
+ {
166
+ let [baseVariant, ...modifiers] = splitAtTopLevelOnly(variant, '/')
167
+
168
+ // This is a hack to support variants with `/` in them, like `ar-1/10/20:text-red-500`
169
+ // In this case 1/10 is a value but /20 is a modifier
170
+ if (modifiers.length > 1) {
171
+ baseVariant = baseVariant + '/' + modifiers.slice(0, -1).join('/')
172
+ modifiers = modifiers.slice(-1)
173
+ }
174
+
175
+ if (modifiers.length && !context.variantMap.has(variant)) {
176
+ variant = baseVariant
177
+ args.modifier = modifiers[0]
178
+
179
+ if (!flagEnabled(context.tailwindConfig, 'generalizedModifiers')) {
180
+ return []
181
+ }
182
+ }
183
+ }
184
+
185
+ // Retrieve "arbitrary value"
186
+ if (variant.endsWith(']') && !variant.startsWith('[')) {
187
+ // We either have:
188
+ // @[200px]
189
+ // group-[:hover]
190
+ //
191
+ // But we don't want:
192
+ // @-[200px] (`-` is incorrect)
193
+ // group[:hover] (`-` is missing)
194
+ let match = /(.)(-?)\[(.*)\]/g.exec(variant)
195
+ if (match) {
196
+ let [, char, seperator, value] = match
197
+ // @-[200px] case
198
+ if (char === '@' && seperator === '-') return []
199
+ // group[:hover] case
200
+ if (char !== '@' && seperator === '') return []
201
+
202
+ variant = variant.replace(`${seperator}[${value}]`, '')
203
+ args.value = value
204
+ }
205
+ }
206
+
207
+ // Register arbitrary variants
208
+ if (isArbitraryValue(variant) && !context.variantMap.has(variant)) {
209
+ let sort = context.offsets.recordVariant(variant)
210
+
211
+ let selector = normalize(variant.slice(1, -1))
212
+ let selectors = splitAtTopLevelOnly(selector, ',')
213
+
214
+ // We do not support multiple selectors for arbitrary variants
215
+ if (selectors.length > 1) {
216
+ return []
217
+ }
218
+
219
+ if (!selectors.every(isValidVariantFormatString)) {
220
+ return []
221
+ }
222
+
223
+ let records = selectors.map((sel, idx) => [
224
+ context.offsets.applyParallelOffset(sort, idx),
225
+ parseVariant(sel.trim()),
226
+ ])
227
+
228
+ context.variantMap.set(variant, records)
229
+ }
230
+
105
231
  if (context.variantMap.has(variant)) {
106
- let variantFunctionTuples = context.variantMap.get(variant)
232
+ let isArbitraryVariant = isArbitraryValue(variant)
233
+ let variantFunctionTuples = context.variantMap.get(variant).slice()
107
234
  let result = []
108
235
 
109
236
  for (let [meta, rule] of matches) {
110
- if (meta.options.respectVariants === false) {
111
- result.push([meta, rule])
237
+ // Don't generate variants for user css
238
+ if (meta.layer === 'user') {
112
239
  continue
113
240
  }
114
241
 
115
242
  let container = postcss.root({ nodes: [rule.clone()] })
116
243
 
117
- for (let [variantSort, variantFunction] of variantFunctionTuples) {
118
- let clone = container.clone()
244
+ for (let [variantSort, variantFunction, containerFromArray] of variantFunctionTuples) {
245
+ let clone = (containerFromArray ?? container).clone()
246
+ let collectedFormats = []
247
+
248
+ function prepareBackup() {
249
+ // Already prepared, chicken out
250
+ if (clone.raws.neededBackup) {
251
+ return
252
+ }
253
+ clone.raws.neededBackup = true
254
+ clone.walkRules((rule) => (rule.raws.originalSelector = rule.selector))
255
+ }
256
+
119
257
  function modifySelectors(modifierFunction) {
258
+ prepareBackup()
120
259
  clone.each((rule) => {
121
260
  if (rule.type !== 'rule') {
122
261
  return
@@ -131,20 +270,126 @@ function applyVariant(variant, matches, context) {
131
270
  })
132
271
  })
133
272
  })
273
+
134
274
  return clone
135
275
  }
136
276
 
137
277
  let ruleWithVariant = variantFunction({
138
- container: clone,
278
+ // Public API
279
+ get container() {
280
+ prepareBackup()
281
+ return clone
282
+ },
139
283
  separator: context.tailwindConfig.separator,
140
284
  modifySelectors,
285
+
286
+ // Private API for now
287
+ wrap(wrapper) {
288
+ let nodes = clone.nodes
289
+ clone.removeAll()
290
+ wrapper.append(nodes)
291
+ clone.append(wrapper)
292
+ },
293
+ format(selectorFormat) {
294
+ collectedFormats.push({
295
+ format: selectorFormat,
296
+ isArbitraryVariant,
297
+ })
298
+ },
299
+ args,
141
300
  })
142
301
 
302
+ // It can happen that a list of format strings is returned from within the function. In that
303
+ // case, we have to process them as well. We can use the existing `variantSort`.
304
+ if (Array.isArray(ruleWithVariant)) {
305
+ for (let [idx, variantFunction] of ruleWithVariant.entries()) {
306
+ // This is a little bit scary since we are pushing to an array of items that we are
307
+ // currently looping over. However, you can also think of it like a processing queue
308
+ // where you keep handling jobs until everything is done and each job can queue more
309
+ // jobs if needed.
310
+ variantFunctionTuples.push([
311
+ context.offsets.applyParallelOffset(variantSort, idx),
312
+ variantFunction,
313
+
314
+ // If the clone has been modified we have to pass that back
315
+ // though so each rule can use the modified container
316
+ clone.clone(),
317
+ ])
318
+ }
319
+ continue
320
+ }
321
+
322
+ if (typeof ruleWithVariant === 'string') {
323
+ collectedFormats.push({
324
+ format: ruleWithVariant,
325
+ isArbitraryVariant,
326
+ })
327
+ }
328
+
143
329
  if (ruleWithVariant === null) {
144
330
  continue
145
331
  }
146
332
 
147
- let withOffset = [{ ...meta, sort: variantSort | meta.sort }, clone.nodes[0]]
333
+ // We had to backup selectors, therefore we assume that somebody touched
334
+ // `container` or `modifySelectors`. Let's see if they did, so that we
335
+ // can restore the selectors, and collect the format strings.
336
+ if (clone.raws.neededBackup) {
337
+ delete clone.raws.neededBackup
338
+ clone.walkRules((rule) => {
339
+ let before = rule.raws.originalSelector
340
+ if (!before) return
341
+ delete rule.raws.originalSelector
342
+ if (before === rule.selector) return // No mutation happened
343
+
344
+ let modified = rule.selector
345
+
346
+ // Rebuild the base selector, this is what plugin authors would do
347
+ // as well. E.g.: `${variant}${separator}${className}`.
348
+ // However, plugin authors probably also prepend or append certain
349
+ // classes, pseudos, ids, ...
350
+ let rebuiltBase = selectorParser((selectors) => {
351
+ selectors.walkClasses((classNode) => {
352
+ classNode.value = `${variant}${context.tailwindConfig.separator}${classNode.value}`
353
+ })
354
+ }).processSync(before)
355
+
356
+ // Now that we know the original selector, the new selector, and
357
+ // the rebuild part in between, we can replace the part that plugin
358
+ // authors need to rebuild with `&`, and eventually store it in the
359
+ // collectedFormats. Similar to what `format('...')` would do.
360
+ //
361
+ // E.g.:
362
+ // variant: foo
363
+ // selector: .markdown > p
364
+ // modified (by plugin): .foo .foo\\:markdown > p
365
+ // rebuiltBase (internal): .foo\\:markdown > p
366
+ // format: .foo &
367
+ collectedFormats.push({
368
+ format: modified.replace(rebuiltBase, '&'),
369
+ isArbitraryVariant,
370
+ })
371
+ rule.selector = before
372
+ })
373
+ }
374
+
375
+ // This tracks the originating layer for the variant
376
+ // For example:
377
+ // .sm:underline {} is a variant of something in the utilities layer
378
+ // .sm:container {} is a variant of the container component
379
+ clone.nodes[0].raws.tailwind = { ...clone.nodes[0].raws.tailwind, parentLayer: meta.layer }
380
+
381
+ let withOffset = [
382
+ {
383
+ ...meta,
384
+ sort: context.offsets.applyVariantOffset(
385
+ meta.sort,
386
+ variantSort,
387
+ Object.assign(args, context.variantOptions.get(variant))
388
+ ),
389
+ collectedFormats: (meta.collectedFormats ?? []).concat(collectedFormats),
390
+ },
391
+ clone.nodes[0],
392
+ ]
148
393
  result.push(withOffset)
149
394
  }
150
395
  }
@@ -174,34 +419,154 @@ function parseRules(rule, cache, options = {}) {
174
419
  return [cache.get(rule), options]
175
420
  }
176
421
 
422
+ const IS_VALID_PROPERTY_NAME = /^[a-z_-]/
423
+
424
+ function isValidPropName(name) {
425
+ return IS_VALID_PROPERTY_NAME.test(name)
426
+ }
427
+
428
+ /**
429
+ * @param {string} declaration
430
+ * @returns {boolean}
431
+ */
432
+ function looksLikeUri(declaration) {
433
+ // Quick bailout for obvious non-urls
434
+ // This doesn't support schemes that don't use a leading // but that's unlikely to be a problem
435
+ if (!declaration.includes('://')) {
436
+ return false
437
+ }
438
+
439
+ try {
440
+ const url = new URL(declaration)
441
+ return url.scheme !== '' && url.host !== ''
442
+ } catch (err) {
443
+ // Definitely not a valid url
444
+ return false
445
+ }
446
+ }
447
+
448
+ function isParsableNode(node) {
449
+ let isParsable = true
450
+
451
+ node.walkDecls((decl) => {
452
+ if (!isParsableCssValue(decl.prop, decl.value)) {
453
+ isParsable = false
454
+ return false
455
+ }
456
+ })
457
+
458
+ return isParsable
459
+ }
460
+
461
+ function isParsableCssValue(property, value) {
462
+ // We don't want to to treat [https://example.com] as a custom property
463
+ // Even though, according to the CSS grammar, it's a totally valid CSS declaration
464
+ // So we short-circuit here by checking if the custom property looks like a url
465
+ if (looksLikeUri(`${property}:${value}`)) {
466
+ return false
467
+ }
468
+
469
+ try {
470
+ postcss.parse(`a{${property}:${value}}`).toResult()
471
+ return true
472
+ } catch (err) {
473
+ return false
474
+ }
475
+ }
476
+
477
+ function extractArbitraryProperty(classCandidate, context) {
478
+ let [, property, value] = classCandidate.match(/^\[([a-zA-Z0-9-_]+):(\S+)\]$/) ?? []
479
+
480
+ if (value === undefined) {
481
+ return null
482
+ }
483
+
484
+ if (!isValidPropName(property)) {
485
+ return null
486
+ }
487
+
488
+ if (!isValidArbitraryValue(value)) {
489
+ return null
490
+ }
491
+
492
+ let normalized = normalize(value)
493
+
494
+ if (!isParsableCssValue(property, normalized)) {
495
+ return null
496
+ }
497
+
498
+ let sort = context.offsets.arbitraryProperty()
499
+
500
+ return [
501
+ [
502
+ { sort, layer: 'utilities' },
503
+ () => ({
504
+ [asClass(classCandidate)]: {
505
+ [property]: normalized,
506
+ },
507
+ }),
508
+ ],
509
+ ]
510
+ }
511
+
177
512
  function* resolveMatchedPlugins(classCandidate, context) {
178
513
  if (context.candidateRuleMap.has(classCandidate)) {
179
514
  yield [context.candidateRuleMap.get(classCandidate), 'DEFAULT']
180
515
  }
181
516
 
517
+ yield* (function* (arbitraryPropertyRule) {
518
+ if (arbitraryPropertyRule !== null) {
519
+ yield [arbitraryPropertyRule, 'DEFAULT']
520
+ }
521
+ })(extractArbitraryProperty(classCandidate, context))
522
+
182
523
  let candidatePrefix = classCandidate
183
524
  let negative = false
184
525
 
185
- const twConfigPrefix = context.tailwindConfig.prefix || ''
526
+ const twConfigPrefix = context.tailwindConfig.prefix
527
+
186
528
  const twConfigPrefixLen = twConfigPrefix.length
187
- if (candidatePrefix[twConfigPrefixLen] === '-') {
529
+
530
+ const hasMatchingPrefix =
531
+ candidatePrefix.startsWith(twConfigPrefix) || candidatePrefix.startsWith(`-${twConfigPrefix}`)
532
+
533
+ if (candidatePrefix[twConfigPrefixLen] === '-' && hasMatchingPrefix) {
188
534
  negative = true
189
535
  candidatePrefix = twConfigPrefix + candidatePrefix.slice(twConfigPrefixLen + 1)
190
536
  }
191
537
 
538
+ if (negative && context.candidateRuleMap.has(candidatePrefix)) {
539
+ yield [context.candidateRuleMap.get(candidatePrefix), '-DEFAULT']
540
+ }
541
+
192
542
  for (let [prefix, modifier] of candidatePermutations(candidatePrefix)) {
193
543
  if (context.candidateRuleMap.has(prefix)) {
194
544
  yield [context.candidateRuleMap.get(prefix), negative ? `-${modifier}` : modifier]
195
- return
196
545
  }
197
546
  }
198
547
  }
199
548
 
200
549
  function splitWithSeparator(input, separator) {
201
- return input.split(new RegExp(`\\${separator}(?![^[]*\\])`, 'g'))
550
+ if (input === sharedState.NOT_ON_DEMAND) {
551
+ return [sharedState.NOT_ON_DEMAND]
552
+ }
553
+
554
+ return splitAtTopLevelOnly(input, separator)
202
555
  }
203
556
 
204
- function* resolveMatches(candidate, context) {
557
+ function* recordCandidates(matches, classCandidate) {
558
+ for (const match of matches) {
559
+ match[1].raws.tailwind = {
560
+ ...match[1].raws.tailwind,
561
+ classCandidate,
562
+ preserveSource: match[0].options?.preserveSource ?? false,
563
+ }
564
+
565
+ yield match
566
+ }
567
+ }
568
+
569
+ function* resolveMatches(candidate, context, original = candidate) {
205
570
  let separator = context.tailwindConfig.separator
206
571
  let [classCandidate, ...variants] = splitWithSeparator(candidate, separator).reverse()
207
572
  let important = false
@@ -211,6 +576,15 @@ function* resolveMatches(candidate, context) {
211
576
  classCandidate = classCandidate.slice(1)
212
577
  }
213
578
 
579
+ if (flagEnabled(context.tailwindConfig, 'variantGrouping')) {
580
+ if (classCandidate.startsWith('(') && classCandidate.endsWith(')')) {
581
+ let base = variants.slice().reverse().join(separator)
582
+ for (let part of splitAtTopLevelOnly(classCandidate.slice(1, -1), ',')) {
583
+ yield* resolveMatches(base + separator + part, context, original)
584
+ }
585
+ }
586
+ }
587
+
214
588
  // TODO: Reintroduce this in ways that doesn't break on false positives
215
589
  // function sortAgainst(toSort, against) {
216
590
  // return toSort.slice().sort((a, z) => {
@@ -225,31 +599,170 @@ function* resolveMatches(candidate, context) {
225
599
 
226
600
  for (let matchedPlugins of resolveMatchedPlugins(classCandidate, context)) {
227
601
  let matches = []
602
+ let typesByMatches = new Map()
603
+
228
604
  let [plugins, modifier] = matchedPlugins
605
+ let isOnlyPlugin = plugins.length === 1
229
606
 
230
607
  for (let [sort, plugin] of plugins) {
608
+ let matchesPerPlugin = []
609
+
231
610
  if (typeof plugin === 'function') {
232
- for (let ruleSet of [].concat(plugin(modifier))) {
611
+ for (let ruleSet of [].concat(plugin(modifier, { isOnlyPlugin }))) {
233
612
  let [rules, options] = parseRules(ruleSet, context.postCssNodeCache)
234
613
  for (let rule of rules) {
235
- matches.push([{ ...sort, options: { ...sort.options, ...options } }, rule])
614
+ matchesPerPlugin.push([{ ...sort, options: { ...sort.options, ...options } }, rule])
236
615
  }
237
616
  }
238
617
  }
239
618
  // Only process static plugins on exact matches
240
- else if (modifier === 'DEFAULT') {
619
+ else if (modifier === 'DEFAULT' || modifier === '-DEFAULT') {
241
620
  let ruleSet = plugin
242
621
  let [rules, options] = parseRules(ruleSet, context.postCssNodeCache)
243
622
  for (let rule of rules) {
244
- matches.push([{ ...sort, options: { ...sort.options, ...options } }, rule])
623
+ matchesPerPlugin.push([{ ...sort, options: { ...sort.options, ...options } }, rule])
624
+ }
625
+ }
626
+
627
+ if (matchesPerPlugin.length > 0) {
628
+ let matchingTypes = Array.from(
629
+ getMatchingTypes(
630
+ sort.options?.types ?? [],
631
+ modifier,
632
+ sort.options ?? {},
633
+ context.tailwindConfig
634
+ )
635
+ ).map(([_, type]) => type)
636
+
637
+ if (matchingTypes.length > 0) {
638
+ typesByMatches.set(matchesPerPlugin, matchingTypes)
639
+ }
640
+
641
+ matches.push(matchesPerPlugin)
642
+ }
643
+ }
644
+
645
+ if (isArbitraryValue(modifier)) {
646
+ if (matches.length > 1) {
647
+ // Partition plugins in 2 categories so that we can start searching in the plugins that
648
+ // don't have `any` as a type first.
649
+ let [withAny, withoutAny] = matches.reduce(
650
+ (group, plugin) => {
651
+ let hasAnyType = plugin.some(([{ options }]) =>
652
+ options.types.some(({ type }) => type === 'any')
653
+ )
654
+
655
+ if (hasAnyType) {
656
+ group[0].push(plugin)
657
+ } else {
658
+ group[1].push(plugin)
659
+ }
660
+ return group
661
+ },
662
+ [[], []]
663
+ )
664
+
665
+ function findFallback(matches) {
666
+ // If only a single plugin matches, let's take that one
667
+ if (matches.length === 1) {
668
+ return matches[0]
669
+ }
670
+
671
+ // Otherwise, find the plugin that creates a valid rule given the arbitrary value, and
672
+ // also has the correct type which preferOnConflicts the plugin in case of clashes.
673
+ return matches.find((rules) => {
674
+ let matchingTypes = typesByMatches.get(rules)
675
+ return rules.some(([{ options }, rule]) => {
676
+ if (!isParsableNode(rule)) {
677
+ return false
678
+ }
679
+
680
+ return options.types.some(
681
+ ({ type, preferOnConflict }) => matchingTypes.includes(type) && preferOnConflict
682
+ )
683
+ })
684
+ })
685
+ }
686
+
687
+ // Try to find a fallback plugin, because we already know that multiple plugins matched for
688
+ // the given arbitrary value.
689
+ let fallback = findFallback(withoutAny) ?? findFallback(withAny)
690
+ if (fallback) {
691
+ matches = [fallback]
692
+ }
693
+
694
+ // We couldn't find a fallback plugin which means that there are now multiple plugins that
695
+ // generated css for the current candidate. This means that the result is ambiguous and this
696
+ // should not happen. We won't generate anything right now, so let's report this to the user
697
+ // by logging some options about what they can do.
698
+ else {
699
+ let typesPerPlugin = matches.map(
700
+ (match) => new Set([...(typesByMatches.get(match) ?? [])])
701
+ )
702
+
703
+ // Remove duplicates, so that we can detect proper unique types for each plugin.
704
+ for (let pluginTypes of typesPerPlugin) {
705
+ for (let type of pluginTypes) {
706
+ let removeFromOwnGroup = false
707
+
708
+ for (let otherGroup of typesPerPlugin) {
709
+ if (pluginTypes === otherGroup) continue
710
+
711
+ if (otherGroup.has(type)) {
712
+ otherGroup.delete(type)
713
+ removeFromOwnGroup = true
714
+ }
715
+ }
716
+
717
+ if (removeFromOwnGroup) pluginTypes.delete(type)
718
+ }
719
+ }
720
+
721
+ let messages = []
722
+
723
+ for (let [idx, group] of typesPerPlugin.entries()) {
724
+ for (let type of group) {
725
+ let rules = matches[idx]
726
+ .map(([, rule]) => rule)
727
+ .flat()
728
+ .map((rule) =>
729
+ rule
730
+ .toString()
731
+ .split('\n')
732
+ .slice(1, -1) // Remove selector and closing '}'
733
+ .map((line) => line.trim())
734
+ .map((x) => ` ${x}`) // Re-indent
735
+ .join('\n')
736
+ )
737
+ .join('\n\n')
738
+
739
+ messages.push(
740
+ ` Use \`${candidate.replace('[', `[${type}:`)}\` for \`${rules.trim()}\``
741
+ )
742
+ break
743
+ }
744
+ }
745
+
746
+ log.warn([
747
+ `The class \`${candidate}\` is ambiguous and matches multiple utilities.`,
748
+ ...messages,
749
+ `If this is content and not a class, replace it with \`${candidate
750
+ .replace('[', '&lsqb;')
751
+ .replace(']', '&rsqb;')}\` to silence this warning.`,
752
+ ])
753
+ continue
245
754
  }
246
755
  }
756
+
757
+ matches = matches.map((list) => list.filter((match) => isParsableNode(match[1])))
247
758
  }
248
759
 
760
+ matches = matches.flat()
761
+ matches = Array.from(recordCandidates(matches, classCandidate))
249
762
  matches = applyPrefix(matches, context)
250
763
 
251
764
  if (important) {
252
- matches = applyImportant(matches, context)
765
+ matches = applyImportant(matches, classCandidate)
253
766
  }
254
767
 
255
768
  for (let variant of variants) {
@@ -257,25 +770,122 @@ function* resolveMatches(candidate, context) {
257
770
  }
258
771
 
259
772
  for (let match of matches) {
773
+ match[1].raws.tailwind = { ...match[1].raws.tailwind, candidate }
774
+
775
+ // Apply final format selector
776
+ match = applyFinalFormat(match, { context, candidate, original })
777
+
778
+ // Skip rules with invalid selectors
779
+ // This will cause the candidate to be added to the "not class"
780
+ // cache skipping it entirely for future builds
781
+ if (match === null) {
782
+ continue
783
+ }
784
+
260
785
  yield match
261
786
  }
262
787
  }
263
788
  }
264
789
 
790
+ function applyFinalFormat(match, { context, candidate, original }) {
791
+ if (!match[0].collectedFormats) {
792
+ return match
793
+ }
794
+
795
+ let isValid = true
796
+ let finalFormat
797
+
798
+ try {
799
+ finalFormat = formatVariantSelector(match[0].collectedFormats, {
800
+ context,
801
+ candidate,
802
+ })
803
+ } catch {
804
+ // The format selector we produced is invalid
805
+ // This could be because:
806
+ // - A bug exists
807
+ // - A plugin introduced an invalid variant selector (ex: `addVariant('foo', '&;foo')`)
808
+ // - The user used an invalid arbitrary variant (ex: `[&;foo]:underline`)
809
+ // Either way the build will fail because of this
810
+ // We would rather that the build pass "silently" given that this could
811
+ // happen because of picking up invalid things when scanning content
812
+ // So we'll throw out the candidate instead
813
+
814
+ return null
815
+ }
816
+
817
+ let container = postcss.root({ nodes: [match[1].clone()] })
818
+
819
+ container.walkRules((rule) => {
820
+ if (inKeyframes(rule)) {
821
+ return
822
+ }
823
+
824
+ try {
825
+ rule.selector = finalizeSelector(rule.selector, finalFormat, {
826
+ candidate: original,
827
+ context,
828
+ })
829
+ } catch {
830
+ // If this selector is invalid we also want to skip it
831
+ // But it's likely that being invalid here means there's a bug in a plugin rather than too loosely matching content
832
+ isValid = false
833
+ return false
834
+ }
835
+ })
836
+
837
+ if (!isValid) {
838
+ return null
839
+ }
840
+
841
+ match[1] = container.nodes[0]
842
+
843
+ return match
844
+ }
845
+
265
846
  function inKeyframes(rule) {
266
847
  return rule.parent && rule.parent.type === 'atrule' && rule.parent.name === 'keyframes'
267
848
  }
268
849
 
850
+ function getImportantStrategy(important) {
851
+ if (important === true) {
852
+ return (rule) => {
853
+ if (inKeyframes(rule)) {
854
+ return
855
+ }
856
+
857
+ rule.walkDecls((d) => {
858
+ if (d.parent.type === 'rule' && !inKeyframes(d.parent)) {
859
+ d.important = true
860
+ }
861
+ })
862
+ }
863
+ }
864
+
865
+ if (typeof important === 'string') {
866
+ return (rule) => {
867
+ if (inKeyframes(rule)) {
868
+ return
869
+ }
870
+
871
+ rule.selectors = rule.selectors.map((selector) => {
872
+ return applyImportantSelector(selector, important)
873
+ })
874
+ }
875
+ }
876
+ }
877
+
269
878
  function generateRules(candidates, context) {
270
879
  let allRules = []
880
+ let strategy = getImportantStrategy(context.tailwindConfig.important)
271
881
 
272
882
  for (let candidate of candidates) {
273
883
  if (context.notClassCache.has(candidate)) {
274
884
  continue
275
885
  }
276
886
 
277
- if (context.classCache.has(candidate)) {
278
- allRules.push(context.classCache.get(candidate))
887
+ if (context.candidateRuleCache.has(candidate)) {
888
+ allRules = allRules.concat(Array.from(context.candidateRuleCache.get(candidate)))
279
889
  continue
280
890
  }
281
891
 
@@ -287,33 +897,31 @@ function generateRules(candidates, context) {
287
897
  }
288
898
 
289
899
  context.classCache.set(candidate, matches)
290
- allRules.push(matches)
291
- }
292
900
 
293
- return allRules.flat(1).map(([{ sort, layer, options }, rule]) => {
294
- if (options.respectImportant) {
295
- if (context.tailwindConfig.important === true) {
296
- rule.walkDecls((d) => {
297
- if (d.parent.type === 'rule' && !inKeyframes(d.parent)) {
298
- d.important = true
299
- }
300
- })
301
- } else if (typeof context.tailwindConfig.important === 'string') {
302
- let container = postcss.root({ nodes: [rule.clone()] })
303
- container.walkRules((r) => {
304
- if (inKeyframes(r)) {
305
- return
306
- }
901
+ let rules = context.candidateRuleCache.get(candidate) ?? new Set()
902
+ context.candidateRuleCache.set(candidate, rules)
307
903
 
308
- r.selectors = r.selectors.map((selector) => {
309
- return `${context.tailwindConfig.important} ${selector}`
310
- })
311
- })
904
+ for (const match of matches) {
905
+ let [{ sort, options }, rule] = match
906
+
907
+ if (options.respectImportant && strategy) {
908
+ let container = postcss.root({ nodes: [rule.clone()] })
909
+ container.walkRules(strategy)
312
910
  rule = container.nodes[0]
313
911
  }
912
+
913
+ let newEntry = [sort, rule]
914
+ rules.add(newEntry)
915
+ context.ruleCache.add(newEntry)
916
+ allRules.push(newEntry)
314
917
  }
315
- return [sort | context.layerOrder[layer], rule]
316
- })
918
+ }
919
+
920
+ return allRules
921
+ }
922
+
923
+ function isArbitraryValue(input) {
924
+ return input.startsWith('[') && input.endsWith(']')
317
925
  }
318
926
 
319
927
  export { resolveMatches, generateRules }