tailwindcss 0.0.0-insiders.fe08e91 → 0.0.0-oxide.6d7c311

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 (186) hide show
  1. package/CHANGELOG.md +384 -3
  2. package/LICENSE +1 -2
  3. package/README.md +12 -8
  4. package/colors.d.ts +3 -0
  5. package/defaultConfig.d.ts +3 -0
  6. package/defaultTheme.d.ts +4 -0
  7. package/lib/cli/build/deps.js +54 -0
  8. package/lib/cli/build/index.js +48 -0
  9. package/lib/cli/build/plugin.js +367 -0
  10. package/lib/cli/build/utils.js +78 -0
  11. package/lib/cli/build/watching.js +178 -0
  12. package/lib/cli/help/index.js +71 -0
  13. package/lib/cli/index.js +239 -0
  14. package/lib/cli/init/index.js +46 -0
  15. package/lib/cli/shared.js +13 -0
  16. package/lib/cli-peer-dependencies.js +20 -7
  17. package/lib/cli.js +4 -740
  18. package/lib/constants.js +27 -20
  19. package/lib/corePluginList.js +6 -3
  20. package/lib/corePlugins.js +2064 -1811
  21. package/lib/css/preflight.css +5 -5
  22. package/lib/featureFlags.js +31 -22
  23. package/lib/index.js +4 -28
  24. package/lib/lib/cacheInvalidation.js +90 -0
  25. package/lib/lib/collapseAdjacentRules.js +27 -9
  26. package/lib/lib/collapseDuplicateDeclarations.js +12 -9
  27. package/lib/lib/content.js +176 -0
  28. package/lib/lib/defaultExtractor.js +225 -31
  29. package/lib/lib/detectNesting.js +13 -10
  30. package/lib/lib/evaluateTailwindFunctions.js +118 -55
  31. package/lib/lib/expandApplyAtRules.js +439 -190
  32. package/lib/lib/expandTailwindAtRules.js +151 -134
  33. package/lib/lib/findAtConfigPath.js +44 -0
  34. package/lib/lib/generateRules.js +454 -187
  35. package/lib/lib/getModuleDependencies.js +11 -8
  36. package/lib/lib/normalizeTailwindDirectives.js +36 -32
  37. package/lib/lib/offsets.js +217 -0
  38. package/lib/lib/partitionApplyAtRules.js +56 -0
  39. package/lib/lib/regex.js +60 -0
  40. package/lib/lib/resolveDefaultsAtRules.js +89 -67
  41. package/lib/lib/setupContextUtils.js +667 -376
  42. package/lib/lib/setupTrackingContext.js +38 -67
  43. package/lib/lib/sharedState.js +27 -14
  44. package/lib/lib/substituteScreenAtRules.js +11 -9
  45. package/lib/oxide/cli.d.js +1 -0
  46. package/lib/oxide/cli.js +2 -0
  47. package/lib/oxide/postcss-plugin.d.js +1 -0
  48. package/lib/oxide/postcss-plugin.js +2 -0
  49. package/lib/plugin.js +48 -0
  50. package/{nesting → lib/postcss-plugins/nesting}/README.md +2 -2
  51. package/lib/postcss-plugins/nesting/index.js +19 -0
  52. package/lib/postcss-plugins/nesting/plugin.js +87 -0
  53. package/lib/processTailwindFeatures.js +35 -25
  54. package/lib/public/colors.js +247 -245
  55. package/lib/public/create-plugin.js +6 -4
  56. package/lib/public/default-config.js +7 -5
  57. package/lib/public/default-theme.js +7 -5
  58. package/lib/public/resolve-config.js +8 -5
  59. package/lib/util/bigSign.js +4 -1
  60. package/lib/util/buildMediaQuery.js +11 -6
  61. package/lib/util/cloneDeep.js +7 -6
  62. package/lib/util/cloneNodes.js +21 -3
  63. package/lib/util/color.js +53 -54
  64. package/lib/util/configurePlugins.js +5 -2
  65. package/lib/util/createPlugin.js +6 -6
  66. package/lib/util/createUtilityPlugin.js +12 -14
  67. package/lib/util/dataTypes.js +119 -110
  68. package/lib/util/defaults.js +4 -1
  69. package/lib/util/escapeClassName.js +7 -4
  70. package/lib/util/escapeCommas.js +5 -2
  71. package/lib/util/flattenColorPalette.js +9 -12
  72. package/lib/util/formatVariantSelector.js +184 -85
  73. package/lib/util/getAllConfigs.js +27 -8
  74. package/lib/util/hashConfig.js +6 -3
  75. package/lib/util/isKeyframeRule.js +5 -2
  76. package/lib/util/isPlainObject.js +5 -2
  77. package/lib/util/{isValidArbitraryValue.js → isSyntacticallyValidPropertyValue.js} +23 -15
  78. package/lib/util/log.js +20 -14
  79. package/lib/util/nameClass.js +20 -9
  80. package/lib/util/negateValue.js +23 -8
  81. package/lib/util/normalizeConfig.js +116 -72
  82. package/lib/util/normalizeScreens.js +120 -11
  83. package/lib/util/parseAnimationValue.js +42 -40
  84. package/lib/util/parseBoxShadowValue.js +30 -23
  85. package/lib/util/parseDependency.js +38 -56
  86. package/lib/util/parseGlob.js +34 -0
  87. package/lib/util/parseObjectStyles.js +11 -8
  88. package/lib/util/pluginUtils.js +147 -50
  89. package/lib/util/prefixSelector.js +10 -8
  90. package/lib/util/removeAlphaVariables.js +29 -0
  91. package/lib/util/resolveConfig.js +97 -85
  92. package/lib/util/resolveConfigPath.js +11 -9
  93. package/lib/util/responsive.js +8 -5
  94. package/lib/util/splitAtTopLevelOnly.js +43 -0
  95. package/lib/util/tap.js +4 -1
  96. package/lib/util/toColorValue.js +5 -3
  97. package/lib/util/toPath.js +20 -4
  98. package/lib/util/transformThemeValue.js +37 -29
  99. package/lib/util/validateConfig.js +24 -0
  100. package/lib/util/validateFormalSyntax.js +24 -0
  101. package/lib/util/withAlphaVariable.js +23 -15
  102. package/nesting/index.js +2 -12
  103. package/package.json +52 -46
  104. package/peers/index.js +11381 -7950
  105. package/plugin.d.ts +11 -0
  106. package/resolveConfig.d.ts +12 -0
  107. package/scripts/generate-types.js +105 -0
  108. package/scripts/release-channel.js +18 -0
  109. package/scripts/release-notes.js +21 -0
  110. package/scripts/type-utils.js +27 -0
  111. package/src/cli/build/deps.js +56 -0
  112. package/src/cli/build/index.js +49 -0
  113. package/src/cli/build/plugin.js +439 -0
  114. package/src/cli/build/utils.js +76 -0
  115. package/src/cli/build/watching.js +227 -0
  116. package/src/cli/help/index.js +70 -0
  117. package/src/cli/index.js +234 -0
  118. package/src/cli/init/index.js +50 -0
  119. package/src/cli/shared.js +6 -0
  120. package/src/cli-peer-dependencies.js +7 -1
  121. package/src/cli.js +4 -810
  122. package/src/corePluginList.js +1 -1
  123. package/src/corePlugins.js +532 -217
  124. package/src/css/preflight.css +5 -5
  125. package/src/featureFlags.js +15 -9
  126. package/src/index.js +4 -27
  127. package/src/lib/cacheInvalidation.js +52 -0
  128. package/src/lib/collapseAdjacentRules.js +21 -2
  129. package/src/lib/content.js +212 -0
  130. package/src/lib/defaultExtractor.js +196 -33
  131. package/src/lib/evaluateTailwindFunctions.js +78 -7
  132. package/src/lib/expandApplyAtRules.js +482 -183
  133. package/src/lib/expandTailwindAtRules.js +106 -85
  134. package/src/lib/findAtConfigPath.js +48 -0
  135. package/src/lib/generateRules.js +418 -129
  136. package/src/lib/normalizeTailwindDirectives.js +1 -0
  137. package/src/lib/offsets.js +270 -0
  138. package/src/lib/partitionApplyAtRules.js +52 -0
  139. package/src/lib/regex.js +74 -0
  140. package/src/lib/resolveDefaultsAtRules.js +51 -30
  141. package/src/lib/setupContextUtils.js +556 -208
  142. package/src/lib/setupTrackingContext.js +11 -48
  143. package/src/lib/sharedState.js +5 -0
  144. package/src/oxide/cli.d.ts +0 -0
  145. package/src/oxide/cli.ts +1 -0
  146. package/src/oxide/postcss-plugin.d.ts +0 -0
  147. package/src/oxide/postcss-plugin.ts +1 -0
  148. package/src/plugin.js +47 -0
  149. package/src/postcss-plugins/nesting/README.md +42 -0
  150. package/src/postcss-plugins/nesting/index.js +13 -0
  151. package/src/postcss-plugins/nesting/plugin.js +80 -0
  152. package/src/processTailwindFeatures.js +8 -0
  153. package/src/util/buildMediaQuery.js +5 -3
  154. package/src/util/cloneNodes.js +19 -2
  155. package/src/util/color.js +25 -21
  156. package/src/util/dataTypes.js +29 -21
  157. package/src/util/formatVariantSelector.js +184 -61
  158. package/src/util/getAllConfigs.js +19 -0
  159. package/src/util/{isValidArbitraryValue.js → isSyntacticallyValidPropertyValue.js} +1 -1
  160. package/src/util/log.js +8 -8
  161. package/src/util/nameClass.js +4 -0
  162. package/src/util/negateValue.js +11 -3
  163. package/src/util/normalizeConfig.js +44 -6
  164. package/src/util/normalizeScreens.js +99 -4
  165. package/src/util/parseBoxShadowValue.js +4 -3
  166. package/src/util/parseDependency.js +37 -42
  167. package/src/util/parseGlob.js +24 -0
  168. package/src/util/pluginUtils.js +132 -10
  169. package/src/util/prefixSelector.js +7 -5
  170. package/src/util/removeAlphaVariables.js +24 -0
  171. package/src/util/resolveConfig.js +70 -32
  172. package/src/util/splitAtTopLevelOnly.js +45 -0
  173. package/src/util/toPath.js +1 -1
  174. package/src/util/transformThemeValue.js +13 -3
  175. package/src/util/validateConfig.js +13 -0
  176. package/src/util/validateFormalSyntax.js +34 -0
  177. package/src/util/withAlphaVariable.js +1 -1
  178. package/stubs/defaultConfig.stub.js +167 -164
  179. package/stubs/simpleConfig.stub.js +1 -0
  180. package/types/config.d.ts +362 -0
  181. package/types/generated/.gitkeep +0 -0
  182. package/types/generated/colors.d.ts +276 -0
  183. package/types/generated/corePluginList.d.ts +1 -0
  184. package/types/generated/default-theme.d.ts +342 -0
  185. package/types/index.d.ts +7 -0
  186. package/nesting/plugin.js +0 -41
@@ -3,18 +3,22 @@ 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, filterSelectorsForClass, getMatchingTypes } from '../util/pluginUtils'
7
7
  import log from '../util/log'
8
+ import * as sharedState from './sharedState'
8
9
  import { formatVariantSelector, finalizeSelector } from '../util/formatVariantSelector'
9
10
  import { asClass } from '../util/nameClass'
10
11
  import { normalize } from '../util/dataTypes'
11
- import isValidArbitraryValue from '../util/isValidArbitraryValue'
12
+ import { isValidVariantFormatString, parseVariant } from './setupContextUtils'
13
+ import isValidArbitraryValue from '../util/isSyntacticallyValidPropertyValue'
14
+ import { splitAtTopLevelOnly } from '../util/splitAtTopLevelOnly.js'
15
+ import { flagEnabled } from '../featureFlags'
12
16
 
13
17
  let classNameParser = selectorParser((selectors) => {
14
18
  return selectors.first.filter(({ type }) => type === 'class').pop().value
15
19
  })
16
20
 
17
- function getClassNameFromSelector(selector) {
21
+ export function getClassNameFromSelector(selector) {
18
22
  return classNameParser.transformSync(selector)
19
23
  }
20
24
 
@@ -25,33 +29,49 @@ function getClassNameFromSelector(selector) {
25
29
  // Example with dynamic classes:
26
30
  // ['grid-cols', '[[linename],1fr,auto]']
27
31
  // ['grid', 'cols-[[linename],1fr,auto]']
28
- function* candidatePermutations(candidate, lastIndex = Infinity) {
29
- if (lastIndex < 0) {
30
- return
31
- }
32
-
33
- let dashIdx
34
-
35
- if (lastIndex === Infinity && candidate.endsWith(']')) {
36
- let bracketIdx = candidate.indexOf('[')
32
+ function* candidatePermutations(candidate) {
33
+ let lastIndex = Infinity
34
+
35
+ while (lastIndex >= 0) {
36
+ let dashIdx
37
+ let wasSlash = false
38
+
39
+ if (lastIndex === Infinity && candidate.endsWith(']')) {
40
+ let bracketIdx = candidate.indexOf('[')
41
+
42
+ // If character before `[` isn't a dash or a slash, this isn't a dynamic class
43
+ // eg. string[]
44
+ if (candidate[bracketIdx - 1] === '-') {
45
+ dashIdx = bracketIdx - 1
46
+ } else if (candidate[bracketIdx - 1] === '/') {
47
+ dashIdx = bracketIdx - 1
48
+ wasSlash = true
49
+ } else {
50
+ dashIdx = -1
51
+ }
52
+ } else if (lastIndex === Infinity && candidate.includes('/')) {
53
+ dashIdx = candidate.lastIndexOf('/')
54
+ wasSlash = true
55
+ } else {
56
+ dashIdx = candidate.lastIndexOf('-', lastIndex)
57
+ }
37
58
 
38
- // If character before `[` isn't a dash or a slash, this isn't a dynamic class
39
- // eg. string[]
40
- dashIdx = ['-', '/'].includes(candidate[bracketIdx - 1]) ? bracketIdx - 1 : -1
41
- } else {
42
- dashIdx = candidate.lastIndexOf('-', lastIndex)
43
- }
59
+ if (dashIdx < 0) {
60
+ break
61
+ }
44
62
 
45
- if (dashIdx < 0) {
46
- return
47
- }
63
+ let prefix = candidate.slice(0, dashIdx)
64
+ let modifier = candidate.slice(wasSlash ? dashIdx : dashIdx + 1)
48
65
 
49
- let prefix = candidate.slice(0, dashIdx)
50
- let modifier = candidate.slice(dashIdx + 1)
66
+ lastIndex = dashIdx - 1
51
67
 
52
- yield [prefix, modifier]
68
+ // TODO: This feels a bit hacky
69
+ if (prefix === '' || modifier === '/') {
70
+ continue
71
+ }
53
72
 
54
- yield* candidatePermutations(candidate, dashIdx - 1)
73
+ yield [prefix, modifier]
74
+ }
55
75
  }
56
76
 
57
77
  function applyPrefix(matches, context) {
@@ -63,9 +83,23 @@ function applyPrefix(matches, context) {
63
83
  let [meta] = match
64
84
  if (meta.options.respectPrefix) {
65
85
  let container = postcss.root({ nodes: [match[1].clone()] })
86
+ let classCandidate = match[1].raws.tailwind.classCandidate
87
+
66
88
  container.walkRules((r) => {
67
- r.selector = prefixSelector(context.tailwindConfig.prefix, r.selector)
89
+ // If this is a negative utility with a dash *before* the prefix we
90
+ // have to ensure that the generated selector matches the candidate
91
+
92
+ // Not doing this will cause `-tw-top-1` to generate the class `.tw--top-1`
93
+ // The disconnect between candidate <-> class can cause @apply to hard crash.
94
+ let shouldPrependNegative = classCandidate.startsWith('-')
95
+
96
+ r.selector = prefixSelector(
97
+ context.tailwindConfig.prefix,
98
+ r.selector,
99
+ shouldPrependNegative
100
+ )
68
101
  })
102
+
69
103
  match[1] = container.nodes[0]
70
104
  }
71
105
  }
@@ -73,7 +107,7 @@ function applyPrefix(matches, context) {
73
107
  return matches
74
108
  }
75
109
 
76
- function applyImportant(matches) {
110
+ function applyImportant(matches, classCandidate) {
77
111
  if (matches.length === 0) {
78
112
  return matches
79
113
  }
@@ -82,9 +116,15 @@ function applyImportant(matches) {
82
116
  for (let [meta, rule] of matches) {
83
117
  let container = postcss.root({ nodes: [rule.clone()] })
84
118
  container.walkRules((r) => {
85
- r.selector = updateAllClasses(r.selector, (className) => {
86
- return `!${className}`
87
- })
119
+ r.selector = updateAllClasses(
120
+ filterSelectorsForClass(r.selector, classCandidate),
121
+ (className) => {
122
+ if (className === classCandidate) {
123
+ return `!${className}`
124
+ }
125
+ return className
126
+ }
127
+ )
88
128
  r.walkDecls((d) => (d.important = true))
89
129
  })
90
130
  result.push([{ ...meta, important: true }, container.nodes[0]])
@@ -107,8 +147,61 @@ function applyVariant(variant, matches, context) {
107
147
  return matches
108
148
  }
109
149
 
150
+ /** @type {{modifier: string | null, value: string | null}} */
151
+ let args = { modifier: null, value: sharedState.NONE }
152
+
153
+ // Retrieve "modifier"
154
+ {
155
+ let match = /(.*)\/(.*)$/g.exec(variant)
156
+ if (match) {
157
+ variant = match[1]
158
+ args.modifier = match[2]
159
+
160
+ if (!flagEnabled(context.tailwindConfig, 'generalizedModifiers')) {
161
+ return []
162
+ }
163
+ }
164
+ }
165
+
166
+ // Retrieve "arbitrary value"
167
+ if (variant.endsWith(']') && !variant.startsWith('[')) {
168
+ // We either have:
169
+ // @[200px]
170
+ // group-[:hover]
171
+ //
172
+ // But we don't want:
173
+ // @-[200px] (`-` is incorrect)
174
+ // group[:hover] (`-` is missing)
175
+ let match = /(.)(-?)\[(.*)\]/g.exec(variant)
176
+ if (match) {
177
+ let [, char, seperator, value] = match
178
+ // @-[200px] case
179
+ if (char === '@' && seperator === '-') return []
180
+ // group[:hover] case
181
+ if (char !== '@' && seperator === '') return []
182
+
183
+ variant = variant.replace(`${seperator}[${value}]`, '')
184
+ args.value = value
185
+ }
186
+ }
187
+
188
+ // Register arbitrary variants
189
+ if (isArbitraryValue(variant) && !context.variantMap.has(variant)) {
190
+ let selector = normalize(variant.slice(1, -1))
191
+
192
+ if (!isValidVariantFormatString(selector)) {
193
+ return []
194
+ }
195
+
196
+ let fn = parseVariant(selector)
197
+
198
+ let sort = context.offsets.recordVariant(variant)
199
+
200
+ context.variantMap.set(variant, [[sort, fn]])
201
+ }
202
+
110
203
  if (context.variantMap.has(variant)) {
111
- let variantFunctionTuples = context.variantMap.get(variant)
204
+ let variantFunctionTuples = context.variantMap.get(variant).slice()
112
205
  let result = []
113
206
 
114
207
  for (let [meta, rule] of matches) {
@@ -119,15 +212,17 @@ function applyVariant(variant, matches, context) {
119
212
 
120
213
  let container = postcss.root({ nodes: [rule.clone()] })
121
214
 
122
- for (let [variantSort, variantFunction] of variantFunctionTuples) {
123
- let clone = container.clone()
215
+ for (let [variantSort, variantFunction, containerFromArray] of variantFunctionTuples) {
216
+ let clone = (containerFromArray ?? container).clone()
124
217
  let collectedFormats = []
125
218
 
126
- let originals = new Map()
127
-
128
219
  function prepareBackup() {
129
- if (originals.size > 0) return // Already prepared, chicken out
130
- clone.walkRules((rule) => originals.set(rule, rule.selector))
220
+ // Already prepared, chicken out
221
+ if (clone.raws.neededBackup) {
222
+ return
223
+ }
224
+ clone.raws.neededBackup = true
225
+ clone.walkRules((rule) => (rule.raws.originalSelector = rule.selector))
131
226
  }
132
227
 
133
228
  function modifySelectors(modifierFunction) {
@@ -169,8 +264,29 @@ function applyVariant(variant, matches, context) {
169
264
  format(selectorFormat) {
170
265
  collectedFormats.push(selectorFormat)
171
266
  },
267
+ args,
172
268
  })
173
269
 
270
+ // It can happen that a list of format strings is returned from within the function. In that
271
+ // case, we have to process them as well. We can use the existing `variantSort`.
272
+ if (Array.isArray(ruleWithVariant)) {
273
+ for (let [idx, variantFunction] of ruleWithVariant.entries()) {
274
+ // This is a little bit scary since we are pushing to an array of items that we are
275
+ // currently looping over. However, you can also think of it like a processing queue
276
+ // where you keep handling jobs until everything is done and each job can queue more
277
+ // jobs if needed.
278
+ variantFunctionTuples.push([
279
+ context.offsets.applyParallelOffset(variantSort, idx),
280
+ variantFunction,
281
+
282
+ // If the clone has been modified we have to pass that back
283
+ // though so each rule can use the modified container
284
+ clone.clone(),
285
+ ])
286
+ }
287
+ continue
288
+ }
289
+
174
290
  if (typeof ruleWithVariant === 'string') {
175
291
  collectedFormats.push(ruleWithVariant)
176
292
  }
@@ -179,13 +295,15 @@ function applyVariant(variant, matches, context) {
179
295
  continue
180
296
  }
181
297
 
182
- // We filled the `originals`, therefore we assume that somebody touched
298
+ // We had to backup selectors, therefore we assume that somebody touched
183
299
  // `container` or `modifySelectors`. Let's see if they did, so that we
184
300
  // can restore the selectors, and collect the format strings.
185
- if (originals.size > 0) {
301
+ if (clone.raws.neededBackup) {
302
+ delete clone.raws.neededBackup
186
303
  clone.walkRules((rule) => {
187
- if (!originals.has(rule)) return
188
- let before = originals.get(rule)
304
+ let before = rule.raws.originalSelector
305
+ if (!before) return
306
+ delete rule.raws.originalSelector
189
307
  if (before === rule.selector) return // No mutation happened
190
308
 
191
309
  let modified = rule.selector
@@ -216,11 +334,22 @@ function applyVariant(variant, matches, context) {
216
334
  })
217
335
  }
218
336
 
337
+ // This tracks the originating layer for the variant
338
+ // For example:
339
+ // .sm:underline {} is a variant of something in the utilities layer
340
+ // .sm:container {} is a variant of the container component
341
+ clone.nodes[0].raws.tailwind = { ...clone.nodes[0].raws.tailwind, parentLayer: meta.layer }
342
+
219
343
  let withOffset = [
220
344
  {
221
345
  ...meta,
222
- sort: variantSort | meta.sort,
346
+ sort: context.offsets.applyVariantOffset(
347
+ meta.sort,
348
+ variantSort,
349
+ Object.assign(args, context.variantOptions.get(variant))
350
+ ),
223
351
  collectedFormats: (meta.collectedFormats ?? []).concat(collectedFormats),
352
+ isArbitraryVariant: isArbitraryValue(variant),
224
353
  },
225
354
  clone.nodes[0],
226
355
  ]
@@ -259,7 +388,47 @@ function isValidPropName(name) {
259
388
  return IS_VALID_PROPERTY_NAME.test(name)
260
389
  }
261
390
 
391
+ /**
392
+ * @param {string} declaration
393
+ * @returns {boolean}
394
+ */
395
+ function looksLikeUri(declaration) {
396
+ // Quick bailout for obvious non-urls
397
+ // This doesn't support schemes that don't use a leading // but that's unlikely to be a problem
398
+ if (!declaration.includes('://')) {
399
+ return false
400
+ }
401
+
402
+ try {
403
+ const url = new URL(declaration)
404
+ return url.scheme !== '' && url.host !== ''
405
+ } catch (err) {
406
+ // Definitely not a valid url
407
+ return false
408
+ }
409
+ }
410
+
411
+ function isParsableNode(node) {
412
+ let isParsable = true
413
+
414
+ node.walkDecls((decl) => {
415
+ if (!isParsableCssValue(decl.name, decl.value)) {
416
+ isParsable = false
417
+ return false
418
+ }
419
+ })
420
+
421
+ return isParsable
422
+ }
423
+
262
424
  function isParsableCssValue(property, value) {
425
+ // We don't want to to treat [https://example.com] as a custom property
426
+ // Even though, according to the CSS grammar, it's a totally valid CSS declaration
427
+ // So we short-circuit here by checking if the custom property looks like a url
428
+ if (looksLikeUri(`${property}:${value}`)) {
429
+ return false
430
+ }
431
+
263
432
  try {
264
433
  postcss.parse(`a{${property}:${value}}`).toResult()
265
434
  return true
@@ -289,9 +458,11 @@ function extractArbitraryProperty(classCandidate, context) {
289
458
  return null
290
459
  }
291
460
 
461
+ let sort = context.offsets.arbitraryProperty()
462
+
292
463
  return [
293
464
  [
294
- { sort: context.arbitraryPropertiesSort, layer: 'utilities' },
465
+ { sort, layer: 'utilities' },
295
466
  () => ({
296
467
  [asClass(classCandidate)]: {
297
468
  [property]: normalized,
@@ -318,7 +489,11 @@ function* resolveMatchedPlugins(classCandidate, context) {
318
489
  const twConfigPrefix = context.tailwindConfig.prefix
319
490
 
320
491
  const twConfigPrefixLen = twConfigPrefix.length
321
- if (candidatePrefix[twConfigPrefixLen] === '-') {
492
+
493
+ const hasMatchingPrefix =
494
+ candidatePrefix.startsWith(twConfigPrefix) || candidatePrefix.startsWith(`-${twConfigPrefix}`)
495
+
496
+ if (candidatePrefix[twConfigPrefixLen] === '-' && hasMatchingPrefix) {
322
497
  negative = true
323
498
  candidatePrefix = twConfigPrefix + candidatePrefix.slice(twConfigPrefixLen + 1)
324
499
  }
@@ -335,10 +510,26 @@ function* resolveMatchedPlugins(classCandidate, context) {
335
510
  }
336
511
 
337
512
  function splitWithSeparator(input, separator) {
338
- return input.split(new RegExp(`\\${separator}(?![^[]*\\])`, 'g'))
513
+ if (input === sharedState.NOT_ON_DEMAND) {
514
+ return [sharedState.NOT_ON_DEMAND]
515
+ }
516
+
517
+ return splitAtTopLevelOnly(input, separator)
518
+ }
519
+
520
+ function* recordCandidates(matches, classCandidate) {
521
+ for (const match of matches) {
522
+ match[1].raws.tailwind = {
523
+ ...match[1].raws.tailwind,
524
+ classCandidate,
525
+ preserveSource: match[0].options?.preserveSource ?? false,
526
+ }
527
+
528
+ yield match
529
+ }
339
530
  }
340
531
 
341
- function* resolveMatches(candidate, context) {
532
+ function* resolveMatches(candidate, context, original = candidate) {
342
533
  let separator = context.tailwindConfig.separator
343
534
  let [classCandidate, ...variants] = splitWithSeparator(candidate, separator).reverse()
344
535
  let important = false
@@ -348,6 +539,15 @@ function* resolveMatches(candidate, context) {
348
539
  classCandidate = classCandidate.slice(1)
349
540
  }
350
541
 
542
+ if (flagEnabled(context.tailwindConfig, 'variantGrouping')) {
543
+ if (classCandidate.startsWith('(') && classCandidate.endsWith(')')) {
544
+ let base = variants.slice().reverse().join(separator)
545
+ for (let part of splitAtTopLevelOnly(classCandidate.slice(1, -1), ',')) {
546
+ yield* resolveMatches(base + separator + part, context, original)
547
+ }
548
+ }
549
+ }
550
+
351
551
  // TODO: Reintroduce this in ways that doesn't break on false positives
352
552
  // function sortAgainst(toSort, against) {
353
553
  // return toSort.slice().sort((a, z) => {
@@ -388,71 +588,144 @@ function* resolveMatches(candidate, context) {
388
588
  }
389
589
 
390
590
  if (matchesPerPlugin.length > 0) {
391
- typesByMatches.set(matchesPerPlugin, sort.options?.type)
591
+ let matchingTypes = Array.from(
592
+ getMatchingTypes(
593
+ sort.options?.types ?? [],
594
+ modifier,
595
+ sort.options ?? {},
596
+ context.tailwindConfig
597
+ )
598
+ ).map(([_, type]) => type)
599
+
600
+ if (matchingTypes.length > 0) {
601
+ typesByMatches.set(matchesPerPlugin, matchingTypes)
602
+ }
603
+
392
604
  matches.push(matchesPerPlugin)
393
605
  }
394
606
  }
395
607
 
396
- // Only keep the result of the very first plugin if we are dealing with
397
- // arbitrary values, to protect against ambiguity.
398
- if (isArbitraryValue(modifier) && matches.length > 1) {
399
- let typesPerPlugin = matches.map((match) => new Set([...(typesByMatches.get(match) ?? [])]))
400
-
401
- // Remove duplicates, so that we can detect proper unique types for each plugin.
402
- for (let pluginTypes of typesPerPlugin) {
403
- for (let type of pluginTypes) {
404
- let removeFromOwnGroup = false
405
-
406
- for (let otherGroup of typesPerPlugin) {
407
- if (pluginTypes === otherGroup) continue
608
+ if (isArbitraryValue(modifier)) {
609
+ if (matches.length > 1) {
610
+ // Partition plugins in 2 categories so that we can start searching in the plugins that
611
+ // don't have `any` as a type first.
612
+ let [withAny, withoutAny] = matches.reduce(
613
+ (group, plugin) => {
614
+ let hasAnyType = plugin.some(([{ options }]) =>
615
+ options.types.some(({ type }) => type === 'any')
616
+ )
408
617
 
409
- if (otherGroup.has(type)) {
410
- otherGroup.delete(type)
411
- removeFromOwnGroup = true
618
+ if (hasAnyType) {
619
+ group[0].push(plugin)
620
+ } else {
621
+ group[1].push(plugin)
412
622
  }
623
+ return group
624
+ },
625
+ [[], []]
626
+ )
627
+
628
+ function findFallback(matches) {
629
+ // If only a single plugin matches, let's take that one
630
+ if (matches.length === 1) {
631
+ return matches[0]
413
632
  }
414
633
 
415
- if (removeFromOwnGroup) pluginTypes.delete(type)
634
+ // Otherwise, find the plugin that creates a valid rule given the arbitrary value, and
635
+ // also has the correct type which preferOnConflicts the plugin in case of clashes.
636
+ return matches.find((rules) => {
637
+ let matchingTypes = typesByMatches.get(rules)
638
+ return rules.some(([{ options }, rule]) => {
639
+ if (!isParsableNode(rule)) {
640
+ return false
641
+ }
642
+
643
+ return options.types.some(
644
+ ({ type, preferOnConflict }) => matchingTypes.includes(type) && preferOnConflict
645
+ )
646
+ })
647
+ })
416
648
  }
417
- }
418
649
 
419
- let messages = []
420
-
421
- for (let [idx, group] of typesPerPlugin.entries()) {
422
- for (let type of group) {
423
- let rules = matches[idx]
424
- .map(([, rule]) => rule)
425
- .flat()
426
- .map((rule) =>
427
- rule
428
- .toString()
429
- .split('\n')
430
- .slice(1, -1) // Remove selector and closing '}'
431
- .map((line) => line.trim())
432
- .map((x) => ` ${x}`) // Re-indent
433
- .join('\n')
434
- )
435
- .join('\n\n')
650
+ // Try to find a fallback plugin, because we already know that multiple plugins matched for
651
+ // the given arbitrary value.
652
+ let fallback = findFallback(withoutAny) ?? findFallback(withAny)
653
+ if (fallback) {
654
+ matches = [fallback]
655
+ }
436
656
 
437
- messages.push(` Use \`${candidate.replace('[', `[${type}:`)}\` for \`${rules.trim()}\``)
438
- break
657
+ // We couldn't find a fallback plugin which means that there are now multiple plugins that
658
+ // generated css for the current candidate. This means that the result is ambiguous and this
659
+ // should not happen. We won't generate anything right now, so let's report this to the user
660
+ // by logging some options about what they can do.
661
+ else {
662
+ let typesPerPlugin = matches.map(
663
+ (match) => new Set([...(typesByMatches.get(match) ?? [])])
664
+ )
665
+
666
+ // Remove duplicates, so that we can detect proper unique types for each plugin.
667
+ for (let pluginTypes of typesPerPlugin) {
668
+ for (let type of pluginTypes) {
669
+ let removeFromOwnGroup = false
670
+
671
+ for (let otherGroup of typesPerPlugin) {
672
+ if (pluginTypes === otherGroup) continue
673
+
674
+ if (otherGroup.has(type)) {
675
+ otherGroup.delete(type)
676
+ removeFromOwnGroup = true
677
+ }
678
+ }
679
+
680
+ if (removeFromOwnGroup) pluginTypes.delete(type)
681
+ }
682
+ }
683
+
684
+ let messages = []
685
+
686
+ for (let [idx, group] of typesPerPlugin.entries()) {
687
+ for (let type of group) {
688
+ let rules = matches[idx]
689
+ .map(([, rule]) => rule)
690
+ .flat()
691
+ .map((rule) =>
692
+ rule
693
+ .toString()
694
+ .split('\n')
695
+ .slice(1, -1) // Remove selector and closing '}'
696
+ .map((line) => line.trim())
697
+ .map((x) => ` ${x}`) // Re-indent
698
+ .join('\n')
699
+ )
700
+ .join('\n\n')
701
+
702
+ messages.push(
703
+ ` Use \`${candidate.replace('[', `[${type}:`)}\` for \`${rules.trim()}\``
704
+ )
705
+ break
706
+ }
707
+ }
708
+
709
+ log.warn([
710
+ `The class \`${candidate}\` is ambiguous and matches multiple utilities.`,
711
+ ...messages,
712
+ `If this is content and not a class, replace it with \`${candidate
713
+ .replace('[', '&lsqb;')
714
+ .replace(']', '&rsqb;')}\` to silence this warning.`,
715
+ ])
716
+ continue
439
717
  }
440
718
  }
441
719
 
442
- log.warn([
443
- `The class \`${candidate}\` is ambiguous and matches multiple utilities.`,
444
- ...messages,
445
- `If this is content and not a class, replace it with \`${candidate
446
- .replace('[', '&lsqb;')
447
- .replace(']', '&rsqb;')}\` to silence this warning.`,
448
- ])
449
- continue
720
+ matches = matches.map((list) => list.filter((match) => isParsableNode(match[1])))
450
721
  }
451
722
 
452
- matches = applyPrefix(matches.flat(), context)
723
+ matches = matches.flat()
724
+ matches = Array.from(recordCandidates(matches, classCandidate))
725
+ matches = applyPrefix(matches, context)
453
726
 
454
727
  if (important) {
455
- matches = applyImportant(matches, context)
728
+ matches = applyImportant(matches, classCandidate)
456
729
  }
457
730
 
458
731
  for (let variant of variants) {
@@ -460,6 +733,8 @@ function* resolveMatches(candidate, context) {
460
733
  }
461
734
 
462
735
  for (let match of matches) {
736
+ match[1].raws.tailwind = { ...match[1].raws.tailwind, candidate }
737
+
463
738
  // Apply final format selector
464
739
  if (match[0].collectedFormats) {
465
740
  let finalFormat = formatVariantSelector('&', ...match[0].collectedFormats)
@@ -469,7 +744,12 @@ function* resolveMatches(candidate, context) {
469
744
 
470
745
  rule.selector = finalizeSelector(finalFormat, {
471
746
  selector: rule.selector,
472
- candidate,
747
+ candidate: original,
748
+ base: candidate
749
+ .split(new RegExp(`\\${context?.tailwindConfig?.separator ?? ':'}(?![^[]*\\])`))
750
+ .pop(),
751
+ isArbitraryVariant: match[0].isArbitraryVariant,
752
+
473
753
  context,
474
754
  })
475
755
  })
@@ -485,16 +765,45 @@ function inKeyframes(rule) {
485
765
  return rule.parent && rule.parent.type === 'atrule' && rule.parent.name === 'keyframes'
486
766
  }
487
767
 
768
+ function getImportantStrategy(important) {
769
+ if (important === true) {
770
+ return (rule) => {
771
+ if (inKeyframes(rule)) {
772
+ return
773
+ }
774
+
775
+ rule.walkDecls((d) => {
776
+ if (d.parent.type === 'rule' && !inKeyframes(d.parent)) {
777
+ d.important = true
778
+ }
779
+ })
780
+ }
781
+ }
782
+
783
+ if (typeof important === 'string') {
784
+ return (rule) => {
785
+ if (inKeyframes(rule)) {
786
+ return
787
+ }
788
+
789
+ rule.selectors = rule.selectors.map((selector) => {
790
+ return `${important} ${selector}`
791
+ })
792
+ }
793
+ }
794
+ }
795
+
488
796
  function generateRules(candidates, context) {
489
797
  let allRules = []
798
+ let strategy = getImportantStrategy(context.tailwindConfig.important)
490
799
 
491
800
  for (let candidate of candidates) {
492
801
  if (context.notClassCache.has(candidate)) {
493
802
  continue
494
803
  }
495
804
 
496
- if (context.classCache.has(candidate)) {
497
- allRules.push(context.classCache.get(candidate))
805
+ if (context.candidateRuleCache.has(candidate)) {
806
+ allRules = allRules.concat(Array.from(context.candidateRuleCache.get(candidate)))
498
807
  continue
499
808
  }
500
809
 
@@ -506,47 +815,27 @@ function generateRules(candidates, context) {
506
815
  }
507
816
 
508
817
  context.classCache.set(candidate, matches)
509
- allRules.push(matches)
510
- }
511
818
 
512
- // Strategy based on `tailwindConfig.important`
513
- let strategy = ((important) => {
514
- if (important === true) {
515
- return (rule) => {
516
- rule.walkDecls((d) => {
517
- if (d.parent.type === 'rule' && !inKeyframes(d.parent)) {
518
- d.important = true
519
- }
520
- })
521
- }
522
- }
819
+ let rules = context.candidateRuleCache.get(candidate) ?? new Set()
820
+ context.candidateRuleCache.set(candidate, rules)
523
821
 
524
- if (typeof important === 'string') {
525
- return (rule) => {
526
- rule.selectors = rule.selectors.map((selector) => {
527
- return `${important} ${selector}`
528
- })
529
- }
530
- }
531
- })(context.tailwindConfig.important)
822
+ for (const match of matches) {
823
+ let [{ sort, options }, rule] = match
532
824
 
533
- return allRules.flat(1).map(([{ sort, layer, options }, rule]) => {
534
- if (options.respectImportant) {
535
- if (strategy) {
825
+ if (options.respectImportant && strategy) {
536
826
  let container = postcss.root({ nodes: [rule.clone()] })
537
- container.walkRules((r) => {
538
- if (inKeyframes(r)) {
539
- return
540
- }
541
-
542
- strategy(r)
543
- })
827
+ container.walkRules(strategy)
544
828
  rule = container.nodes[0]
545
829
  }
830
+
831
+ let newEntry = [sort, rule]
832
+ rules.add(newEntry)
833
+ context.ruleCache.add(newEntry)
834
+ allRules.push(newEntry)
546
835
  }
836
+ }
547
837
 
548
- return [sort | context.layerOrder[layer], rule]
549
- })
838
+ return allRules
550
839
  }
551
840
 
552
841
  function isArbitraryValue(input) {