tailwindcss 0.0.0-insiders.7c337f2 → 0.0.0-insiders.7d8eb21

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 (161) hide show
  1. package/CHANGELOG.md +246 -6
  2. package/README.md +13 -9
  3. package/defaultTheme.d.ts +2 -1
  4. package/lib/cli/build/deps.js +54 -0
  5. package/lib/cli/build/index.js +48 -0
  6. package/lib/cli/build/plugin.js +362 -0
  7. package/lib/cli/build/utils.js +78 -0
  8. package/lib/cli/build/watching.js +178 -0
  9. package/lib/cli/help/index.js +71 -0
  10. package/lib/cli/index.js +18 -0
  11. package/lib/cli/init/index.js +46 -0
  12. package/lib/cli/shared.js +12 -0
  13. package/lib/cli-peer-dependencies.js +18 -5
  14. package/lib/cli.js +62 -592
  15. package/lib/constants.js +19 -12
  16. package/lib/corePluginList.js +5 -3
  17. package/lib/corePlugins.js +619 -347
  18. package/lib/css/preflight.css +7 -0
  19. package/lib/featureFlags.js +34 -22
  20. package/lib/index.js +16 -9
  21. package/lib/lib/cacheInvalidation.js +42 -21
  22. package/lib/lib/collapseAdjacentRules.js +16 -14
  23. package/lib/lib/collapseDuplicateDeclarations.js +13 -10
  24. package/lib/lib/content.js +177 -0
  25. package/lib/lib/defaultExtractor.js +230 -33
  26. package/lib/lib/detectNesting.js +13 -4
  27. package/lib/lib/evaluateTailwindFunctions.js +105 -42
  28. package/lib/lib/expandApplyAtRules.js +151 -92
  29. package/lib/lib/expandTailwindAtRules.js +134 -149
  30. package/lib/lib/findAtConfigPath.js +44 -0
  31. package/lib/lib/generateRules.js +404 -185
  32. package/lib/lib/getModuleDependencies.js +21 -18
  33. package/lib/lib/normalizeTailwindDirectives.js +11 -8
  34. package/lib/lib/offsets.js +285 -0
  35. package/lib/lib/partitionApplyAtRules.js +14 -11
  36. package/lib/lib/regex.js +60 -0
  37. package/lib/lib/remap-bitfield.js +87 -0
  38. package/lib/lib/resolveDefaultsAtRules.js +97 -79
  39. package/lib/lib/setupContextUtils.js +567 -242
  40. package/lib/lib/setupTrackingContext.js +77 -106
  41. package/lib/lib/sharedState.js +18 -10
  42. package/lib/lib/substituteScreenAtRules.js +9 -7
  43. package/lib/postcss-plugins/nesting/index.js +7 -5
  44. package/lib/postcss-plugins/nesting/plugin.js +10 -8
  45. package/lib/processTailwindFeatures.js +34 -31
  46. package/lib/public/colors.js +7 -5
  47. package/lib/public/create-plugin.js +6 -4
  48. package/lib/public/default-config.js +7 -5
  49. package/lib/public/default-theme.js +7 -5
  50. package/lib/public/resolve-config.js +13 -10
  51. package/lib/util/bigSign.js +4 -1
  52. package/lib/util/buildMediaQuery.js +10 -5
  53. package/lib/util/cloneDeep.js +6 -5
  54. package/lib/util/cloneNodes.js +9 -2
  55. package/lib/util/color.js +20 -17
  56. package/lib/util/configurePlugins.js +5 -2
  57. package/lib/util/createPlugin.js +6 -5
  58. package/lib/util/createUtilityPlugin.js +15 -13
  59. package/lib/util/dataTypes.js +75 -51
  60. package/lib/util/defaults.js +8 -5
  61. package/lib/util/escapeClassName.js +13 -10
  62. package/lib/util/escapeCommas.js +4 -1
  63. package/lib/util/flattenColorPalette.js +7 -8
  64. package/lib/util/formatVariantSelector.js +270 -103
  65. package/lib/util/getAllConfigs.js +34 -15
  66. package/lib/util/hashConfig.js +10 -7
  67. package/lib/util/isKeyframeRule.js +4 -1
  68. package/lib/util/isPlainObject.js +4 -1
  69. package/lib/util/{isValidArbitraryValue.js → isSyntacticallyValidPropertyValue.js} +34 -26
  70. package/lib/util/log.js +14 -8
  71. package/lib/util/nameClass.js +19 -8
  72. package/lib/util/negateValue.js +20 -5
  73. package/lib/util/normalizeConfig.js +94 -53
  74. package/lib/util/normalizeScreens.js +115 -4
  75. package/lib/util/parseAnimationValue.js +43 -41
  76. package/lib/util/parseBoxShadowValue.js +12 -45
  77. package/lib/util/parseDependency.js +37 -55
  78. package/lib/util/parseGlob.js +34 -0
  79. package/lib/util/parseObjectStyles.js +15 -12
  80. package/lib/util/pluginUtils.js +146 -42
  81. package/lib/util/prefixSelector.js +26 -9
  82. package/lib/util/removeAlphaVariables.js +29 -0
  83. package/lib/util/resolveConfig.js +104 -105
  84. package/lib/util/resolveConfigPath.js +20 -17
  85. package/lib/util/responsive.js +12 -9
  86. package/lib/util/splitAtTopLevelOnly.js +43 -0
  87. package/lib/util/tap.js +4 -1
  88. package/lib/util/toColorValue.js +4 -1
  89. package/lib/util/toPath.js +18 -2
  90. package/lib/util/transformThemeValue.js +23 -11
  91. package/lib/util/validateConfig.js +24 -0
  92. package/lib/util/validateFormalSyntax.js +24 -0
  93. package/lib/util/withAlphaVariable.js +32 -24
  94. package/package.json +32 -29
  95. package/peers/index.js +6693 -3036
  96. package/plugin.d.ts +10 -5
  97. package/resolveConfig.d.ts +12 -0
  98. package/scripts/generate-types.js +53 -0
  99. package/scripts/release-channel.js +18 -0
  100. package/scripts/release-notes.js +21 -0
  101. package/scripts/type-utils.js +27 -0
  102. package/src/cli/build/deps.js +56 -0
  103. package/src/cli/build/index.js +49 -0
  104. package/src/cli/build/plugin.js +434 -0
  105. package/src/cli/build/utils.js +76 -0
  106. package/src/cli/build/watching.js +227 -0
  107. package/src/cli/help/index.js +70 -0
  108. package/src/cli/index.js +3 -0
  109. package/src/cli/init/index.js +50 -0
  110. package/src/cli/shared.js +5 -0
  111. package/src/cli-peer-dependencies.js +7 -1
  112. package/src/cli.js +46 -659
  113. package/src/corePlugins.js +346 -79
  114. package/src/css/preflight.css +7 -0
  115. package/src/featureFlags.js +12 -2
  116. package/src/index.js +5 -0
  117. package/src/lib/content.js +213 -0
  118. package/src/lib/defaultExtractor.js +202 -35
  119. package/src/lib/detectNesting.js +9 -1
  120. package/src/lib/evaluateTailwindFunctions.js +78 -7
  121. package/src/lib/expandApplyAtRules.js +129 -34
  122. package/src/lib/expandTailwindAtRules.js +12 -50
  123. package/src/lib/findAtConfigPath.js +48 -0
  124. package/src/lib/generateRules.js +379 -126
  125. package/src/lib/offsets.js +351 -0
  126. package/src/lib/regex.js +74 -0
  127. package/src/lib/remap-bitfield.js +82 -0
  128. package/src/lib/resolveDefaultsAtRules.js +53 -36
  129. package/src/lib/setupContextUtils.js +510 -120
  130. package/src/lib/setupTrackingContext.js +8 -45
  131. package/src/lib/sharedState.js +2 -0
  132. package/src/util/buildMediaQuery.js +5 -3
  133. package/src/util/cloneNodes.js +5 -1
  134. package/src/util/dataTypes.js +51 -26
  135. package/src/util/formatVariantSelector.js +317 -101
  136. package/src/util/getAllConfigs.js +19 -0
  137. package/src/util/{isValidArbitraryValue.js → isSyntacticallyValidPropertyValue.js} +1 -1
  138. package/src/util/log.js +1 -1
  139. package/src/util/nameClass.js +4 -0
  140. package/src/util/negateValue.js +10 -2
  141. package/src/util/normalizeConfig.js +40 -10
  142. package/src/util/normalizeScreens.js +99 -4
  143. package/src/util/parseBoxShadowValue.js +3 -50
  144. package/src/util/parseDependency.js +37 -42
  145. package/src/util/parseGlob.js +24 -0
  146. package/src/util/pluginUtils.js +132 -10
  147. package/src/util/prefixSelector.js +28 -10
  148. package/src/util/removeAlphaVariables.js +24 -0
  149. package/src/util/resolveConfig.js +70 -62
  150. package/src/util/splitAtTopLevelOnly.js +45 -0
  151. package/src/util/toPath.js +1 -1
  152. package/src/util/transformThemeValue.js +13 -3
  153. package/src/util/validateConfig.js +13 -0
  154. package/src/util/validateFormalSyntax.js +34 -0
  155. package/src/util/withAlphaVariable.js +1 -1
  156. package/stubs/defaultConfig.stub.js +165 -164
  157. package/stubs/simpleConfig.stub.js +1 -0
  158. package/types/config.d.ts +77 -37
  159. package/types/generated/default-theme.d.ts +342 -0
  160. package/types/index.d.ts +7 -1
  161. package/types.d.ts +0 -1
@@ -3,33 +3,153 @@ import unescape from 'postcss-selector-parser/dist/util/unesc'
3
3
  import escapeClassName from '../util/escapeClassName'
4
4
  import prefixSelector from '../util/prefixSelector'
5
5
 
6
+ /** @typedef {import('postcss-selector-parser').Root} Root */
7
+ /** @typedef {import('postcss-selector-parser').Selector} Selector */
8
+ /** @typedef {import('postcss-selector-parser').Pseudo} Pseudo */
9
+ /** @typedef {import('postcss-selector-parser').Node} Node */
10
+
11
+ /** @typedef {{format: string, isArbitraryVariant: boolean}[]} RawFormats */
12
+ /** @typedef {import('postcss-selector-parser').Root} ParsedFormats */
13
+ /** @typedef {RawFormats | ParsedFormats} AcceptedFormats */
14
+
6
15
  let MERGE = ':merge'
7
- let PARENT = '&'
8
16
 
9
- export let selectorFunctions = new Set([MERGE])
17
+ /**
18
+ * @param {RawFormats} formats
19
+ * @param {{context: any, candidate: string, base: string | null}} options
20
+ * @returns {ParsedFormats | null}
21
+ */
22
+ export function formatVariantSelector(formats, { context, candidate }) {
23
+ let prefix = context?.tailwindConfig.prefix ?? ''
24
+
25
+ // Parse the format selector into an AST
26
+ let parsedFormats = formats.map((format) => {
27
+ let ast = selectorParser().astSync(format.format)
28
+
29
+ return {
30
+ ...format,
31
+ ast: format.isArbitraryVariant ? ast : prefixSelector(prefix, ast),
32
+ }
33
+ })
34
+
35
+ // We start with the candidate selector
36
+ let formatAst = selectorParser.root({
37
+ nodes: [
38
+ selectorParser.selector({
39
+ nodes: [selectorParser.className({ value: escapeClassName(candidate) })],
40
+ }),
41
+ ],
42
+ })
43
+
44
+ // And iteratively merge each format selector into the candidate selector
45
+ for (let { ast } of parsedFormats) {
46
+ // 1. Handle :merge() special pseudo-class
47
+ ;[formatAst, ast] = handleMergePseudo(formatAst, ast)
48
+
49
+ // 2. Merge the format selector into the current selector AST
50
+ ast.walkNesting((nesting) => nesting.replaceWith(...formatAst.nodes[0].nodes))
51
+
52
+ // 3. Keep going!
53
+ formatAst = ast
54
+ }
55
+
56
+ return formatAst
57
+ }
58
+
59
+ /**
60
+ * Given any node in a selector this gets the "simple" selector it's a part of
61
+ * A simple selector is just a list of nodes without any combinators
62
+ * Technically :is(), :not(), :has(), etc… can have combinators but those are nested
63
+ * inside the relevant node and won't be picked up so they're fine to ignore
64
+ *
65
+ * @param {Node} node
66
+ * @returns {Node[]}
67
+ **/
68
+ function simpleSelectorForNode(node) {
69
+ /** @type {Node[]} */
70
+ let nodes = []
71
+
72
+ // Walk backwards until we hit a combinator node (or the start)
73
+ while (node.prev() && node.prev().type !== 'combinator') {
74
+ node = node.prev()
75
+ }
76
+
77
+ // Now record all non-combinator nodes until we hit one (or the end)
78
+ while (node && node.type !== 'combinator') {
79
+ nodes.push(node)
80
+ node = node.next()
81
+ }
10
82
 
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]
83
+ return nodes
84
+ }
20
85
 
21
- current = current.replace(existingTarget, existingTarget + addition)
22
- continue
23
- }
86
+ /**
87
+ * Resorts the nodes in a selector to ensure they're in the correct order
88
+ * Tags go before classes, and pseudo classes go after classes
89
+ *
90
+ * @param {Selector} sel
91
+ * @returns {Selector}
92
+ **/
93
+ function resortSelector(sel) {
94
+ sel.sort((a, b) => {
95
+ if (a.type === 'tag' && b.type === 'class') {
96
+ return -1
97
+ } else if (a.type === 'class' && b.type === 'tag') {
98
+ return 1
99
+ } else if (a.type === 'class' && b.type === 'pseudo' && b.value.startsWith('::')) {
100
+ return -1
101
+ } else if (a.type === 'pseudo' && a.value.startsWith('::') && b.type === 'class') {
102
+ return 1
24
103
  }
25
104
 
26
- current = other.replace(PARENT, current)
105
+ return sel.index(a) - sel.index(b)
106
+ })
107
+
108
+ return sel
109
+ }
110
+
111
+ /**
112
+ * Remove extraneous selectors that do not include the base class/candidate
113
+ *
114
+ * Example:
115
+ * Given the utility `.a, .b { color: red}`
116
+ * Given the candidate `sm:b`
117
+ *
118
+ * The final selector should be `.sm\:b` and not `.a, .sm\:b`
119
+ *
120
+ * @param {Selector} ast
121
+ * @param {string} base
122
+ */
123
+ function eliminateIrrelevantSelectors(sel, base) {
124
+ let hasClassesMatchingCandidate = false
125
+
126
+ sel.walk((child) => {
127
+ if (child.type === 'class' && child.value === base) {
128
+ hasClassesMatchingCandidate = true
129
+ return false // Stop walking
130
+ }
131
+ })
132
+
133
+ if (!hasClassesMatchingCandidate) {
134
+ sel.remove()
27
135
  }
28
136
 
29
- return current
137
+ // We do NOT recursively eliminate sub selectors that don't have the base class
138
+ // as this is NOT a safe operation. For example, if we have:
139
+ // `.space-x-2 > :not([hidden]) ~ :not([hidden])`
140
+ // We cannot remove the [hidden] from the :not() because it would change the
141
+ // meaning of the selector.
142
+
143
+ // TODO: Can we do this for :matches, :is, and :where?
30
144
  }
31
145
 
32
- export function finalizeSelector(format, { selector, candidate, context }) {
146
+ /**
147
+ * @param {string} current
148
+ * @param {AcceptedFormats} formats
149
+ * @param {{context: any, candidate: string, base: string | null}} options
150
+ * @returns {string}
151
+ */
152
+ export function finalizeSelector(current, formats, { context, candidate, base }) {
33
153
  let separator = context?.tailwindConfig?.separator ?? ':'
34
154
 
35
155
  // Split by the separator, but ignore the separator inside square brackets:
@@ -39,14 +159,10 @@ export function finalizeSelector(format, { selector, candidate, context }) {
39
159
  // │ │ │ ╰── We will not split here
40
160
  // ╰──┴─────┴─────────────── We will split here
41
161
  //
42
- let splitter = new RegExp(`\\${separator}(?![^[]*\\])`)
43
- let base = candidate.split(splitter).pop()
162
+ base = base ?? candidate.split(new RegExp(`\\${separator}(?![^[]*\\])`)).pop()
44
163
 
45
- if (context?.tailwindConfig?.prefix) {
46
- format = prefixSelector(context.tailwindConfig.prefix, format)
47
- }
48
-
49
- format = format.replace(PARENT, `.${escapeClassName(candidate)}`)
164
+ // Parse the selector into an AST
165
+ let selector = selectorParser().astSync(current)
50
166
 
51
167
  // Normalize escaped classes, e.g.:
52
168
  //
@@ -59,65 +175,148 @@ export function finalizeSelector(format, { selector, candidate, context }) {
59
175
  // base in selector: bg-\\[rgb\\(255\\,0\\,0\\)\\]
60
176
  // escaped base: bg-\\[rgb\\(255\\2c 0\\2c 0\\)\\]
61
177
  //
62
- selector = selectorParser((selectors) => {
63
- return selectors.walkClasses((node) => {
64
- if (node.raws && node.value.includes(base)) {
65
- node.raws.value = escapeClassName(unescape(node.raws.value))
66
- }
178
+ selector.walkClasses((node) => {
179
+ if (node.raws && node.value.includes(base)) {
180
+ node.raws.value = escapeClassName(unescape(node.raws.value))
181
+ }
182
+ })
183
+
184
+ // Remove extraneous selectors that do not include the base candidate
185
+ selector.each((sel) => eliminateIrrelevantSelectors(sel, base))
67
186
 
68
- return node
69
- })
70
- }).processSync(selector)
187
+ // If there are no formats that means there were no variants added to the candidate
188
+ // so we can just return the selector as-is
189
+ let formatAst = Array.isArray(formats)
190
+ ? formatVariantSelector(formats, { context, candidate })
191
+ : formats
192
+
193
+ if (formatAst === null) {
194
+ return selector.toString()
195
+ }
196
+
197
+ let simpleStart = selectorParser.comment({ value: '/*__simple__*/' })
198
+ let simpleEnd = selectorParser.comment({ value: '/*__simple__*/' })
71
199
 
72
200
  // We can safely replace the escaped base now, since the `base` section is
73
201
  // now in a normalized escaped value.
74
- selector = selector.replace(`.${escapeClassName(base)}`, format)
202
+ selector.walkClasses((node) => {
203
+ if (node.value !== base) {
204
+ return
205
+ }
206
+
207
+ let parent = node.parent
208
+ let formatNodes = formatAst.nodes[0].nodes
209
+
210
+ // Perf optimization: if the parent is a single class we can just replace it and be done
211
+ if (parent.nodes.length === 1) {
212
+ node.replaceWith(...formatNodes)
213
+ return
214
+ }
215
+
216
+ let simpleSelector = simpleSelectorForNode(node)
217
+ parent.insertBefore(simpleSelector[0], simpleStart)
218
+ parent.insertAfter(simpleSelector[simpleSelector.length - 1], simpleEnd)
219
+
220
+ for (let child of formatNodes) {
221
+ parent.insertBefore(simpleSelector[0], child)
222
+ }
223
+
224
+ node.remove()
225
+
226
+ // Re-sort the simple selector to ensure it's in the correct order
227
+ simpleSelector = simpleSelectorForNode(simpleStart)
228
+ let firstNode = parent.index(simpleStart)
229
+
230
+ parent.nodes.splice(
231
+ firstNode,
232
+ simpleSelector.length,
233
+ ...resortSelector(selectorParser.selector({ nodes: simpleSelector })).nodes
234
+ )
235
+
236
+ simpleStart.remove()
237
+ simpleEnd.remove()
238
+ })
75
239
 
76
240
  // Remove unnecessary pseudo selectors that we used as placeholders
77
- return selectorParser((selectors) => {
78
- return selectors.map((selector) => {
79
- selector.walkPseudos((p) => {
80
- if (selectorFunctions.has(p.value)) {
81
- p.replaceWith(p.nodes)
82
- }
83
-
84
- return p
241
+ selector.walkPseudos((p) => {
242
+ if (p.value === MERGE) {
243
+ p.replaceWith(p.nodes)
244
+ }
245
+ })
246
+
247
+ // Move pseudo elements to the end of the selector (if necessary)
248
+ selector.each((sel) => {
249
+ let pseudoElements = collectPseudoElements(sel)
250
+ if (pseudoElements.length > 0) {
251
+ sel.nodes.push(pseudoElements.sort(sortSelector))
252
+ }
253
+ })
254
+
255
+ return selector.toString()
256
+ }
257
+
258
+ /**
259
+ *
260
+ * @param {Selector} selector
261
+ * @param {Selector} format
262
+ */
263
+ export function handleMergePseudo(selector, format) {
264
+ /** @type {{pseudo: Pseudo, value: string}[]} */
265
+ let merges = []
266
+
267
+ // Find all :merge() pseudo-classes in `selector`
268
+ selector.walkPseudos((pseudo) => {
269
+ if (pseudo.value === MERGE) {
270
+ merges.push({
271
+ pseudo,
272
+ value: pseudo.nodes[0].toString(),
85
273
  })
274
+ }
275
+ })
276
+
277
+ // Find all :merge() "attachments" in `format` and attach them to the matching selector in `selector`
278
+ format.walkPseudos((pseudo) => {
279
+ if (pseudo.value !== MERGE) {
280
+ return
281
+ }
282
+
283
+ let value = pseudo.nodes[0].toString()
284
+
285
+ // Does `selector` contain a :merge() pseudo-class with the same value?
286
+ let existing = merges.find((merge) => merge.value === value)
287
+
288
+ // Nope so there's nothing to do
289
+ if (!existing) {
290
+ return
291
+ }
292
+
293
+ // Everything after `:merge()` up to the next combinator is what is attached to the merged selector
294
+ let attachments = []
295
+ let next = pseudo.next()
296
+ while (next && next.type !== 'combinator') {
297
+ attachments.push(next)
298
+ next = next.next()
299
+ }
300
+
301
+ let combinator = next
302
+
303
+ existing.pseudo.parent.insertAfter(
304
+ existing.pseudo,
305
+ selectorParser.selector({ nodes: attachments.map((node) => node.clone()) })
306
+ )
86
307
 
87
- // This will make sure to move pseudo's to the correct spot (the end for
88
- // pseudo elements) because otherwise the selector will never work
89
- // anyway.
90
- //
91
- // E.g.:
92
- // - `before:hover:text-center` would result in `.before\:hover\:text-center:hover::before`
93
- // - `hover:before:text-center` would result in `.hover\:before\:text-center:hover::before`
94
- //
95
- // `::before:hover` doesn't work, which means that we can make it work for you by flipping the order.
96
- function collectPseudoElements(selector) {
97
- let nodes = []
98
-
99
- for (let node of selector.nodes) {
100
- if (isPseudoElement(node)) {
101
- nodes.push(node)
102
- selector.removeChild(node)
103
- }
104
-
105
- if (node?.nodes) {
106
- nodes.push(...collectPseudoElements(node))
107
- }
108
- }
109
-
110
- return nodes
111
- }
112
-
113
- let pseudoElements = collectPseudoElements(selector)
114
- if (pseudoElements.length > 0) {
115
- selector.nodes.push(pseudoElements.sort(sortSelector))
116
- }
117
-
118
- return selector
119
- })
120
- }).processSync(selector)
308
+ pseudo.remove()
309
+ attachments.forEach((node) => node.remove())
310
+
311
+ // What about this case:
312
+ // :merge(.group):focus > &
313
+ // :merge(.group):hover &
314
+ if (combinator && combinator.type === 'combinator') {
315
+ combinator.remove()
316
+ }
317
+ })
318
+
319
+ return [selector, format]
121
320
  }
122
321
 
123
322
  // Note: As a rule, double colons (::) should be used instead of a single colon
@@ -127,7 +326,49 @@ export function finalizeSelector(format, { selector, candidate, context }) {
127
326
  let pseudoElementsBC = [':before', ':after', ':first-line', ':first-letter']
128
327
 
129
328
  // These pseudo-elements _can_ be combined with other pseudo selectors AND the order does matter.
130
- let pseudoElementExceptions = ['::file-selector-button']
329
+ let pseudoElementExceptions = [
330
+ '::file-selector-button',
331
+
332
+ // Webkit scroll bar pseudo elements can be combined with user-action pseudo classes
333
+ '::-webkit-scrollbar',
334
+ '::-webkit-scrollbar-button',
335
+ '::-webkit-scrollbar-thumb',
336
+ '::-webkit-scrollbar-track',
337
+ '::-webkit-scrollbar-track-piece',
338
+ '::-webkit-scrollbar-corner',
339
+ '::-webkit-resizer',
340
+ ]
341
+
342
+ /**
343
+ * This will make sure to move pseudo's to the correct spot (the end for
344
+ * pseudo elements) because otherwise the selector will never work
345
+ * anyway.
346
+ *
347
+ * E.g.:
348
+ * - `before:hover:text-center` would result in `.before\:hover\:text-center:hover::before`
349
+ * - `hover:before:text-center` would result in `.hover\:before\:text-center:hover::before`
350
+ *
351
+ * `::before:hover` doesn't work, which means that we can make it work for you by flipping the order.
352
+ *
353
+ * @param {Selector} selector
354
+ **/
355
+ function collectPseudoElements(selector) {
356
+ /** @type {Node[]} */
357
+ let nodes = []
358
+
359
+ for (let node of selector.nodes) {
360
+ if (isPseudoElement(node)) {
361
+ nodes.push(node)
362
+ selector.removeChild(node)
363
+ }
364
+
365
+ if (node?.nodes) {
366
+ nodes.push(...collectPseudoElements(node))
367
+ }
368
+ }
369
+
370
+ return nodes
371
+ }
131
372
 
132
373
  // This will make sure to move pseudo's to the correct spot (the end for
133
374
  // pseudo elements) because otherwise the selector will never work
@@ -169,28 +410,3 @@ function isPseudoElement(node) {
169
410
 
170
411
  return node.value.startsWith('::') || pseudoElementsBC.includes(node.value)
171
412
  }
172
-
173
- function resolveFunctionArgument(haystack, needle, arg) {
174
- let startIdx = haystack.indexOf(arg ? `${needle}(${arg})` : needle)
175
- if (startIdx === -1) return null
176
-
177
- // Start inside the `(`
178
- startIdx += needle.length + 1
179
-
180
- let target = ''
181
- let count = 0
182
-
183
- for (let char of haystack.slice(startIdx)) {
184
- if (char !== '(' && char !== ')') {
185
- target += char
186
- } else if (char === '(') {
187
- target += char
188
- count++
189
- } else if (char === ')') {
190
- if (--count < 0) break // unbalanced
191
- target += char
192
- }
193
- }
194
-
195
- return target
196
- }
@@ -9,6 +9,25 @@ export default function getAllConfigs(config) {
9
9
 
10
10
  const features = {
11
11
  // Add experimental configs here...
12
+ respectDefaultRingColorOpacity: {
13
+ theme: {
14
+ ringColor: ({ theme }) => ({
15
+ DEFAULT: '#3b82f67f',
16
+ ...theme('colors'),
17
+ }),
18
+ },
19
+ },
20
+
21
+ disableColorOpacityUtilitiesByDefault: {
22
+ corePlugins: {
23
+ backgroundOpacity: false,
24
+ borderOpacity: false,
25
+ divideOpacity: false,
26
+ placeholderOpacity: false,
27
+ ringOpacity: false,
28
+ textOpacity: false,
29
+ },
30
+ },
12
31
  }
13
32
 
14
33
  const experimentals = Object.keys(features)
@@ -15,7 +15,7 @@ let quotes = new Set(['"', "'", '`'])
15
15
  // E.g.: w-[this-is]w-[weird-and-invalid]
16
16
  // E.g.: w-[this-is\\]w-\\[weird-but-valid]
17
17
  // E.g.: content-['this-is-also-valid]-weirdly-enough']
18
- export default function isValidArbitraryValue(value) {
18
+ export default function isSyntacticallyValidPropertyValue(value) {
19
19
  let stack = []
20
20
  let inQuotes = false
21
21
 
package/src/util/log.js CHANGED
@@ -3,7 +3,7 @@ import colors from 'picocolors'
3
3
  let alreadyShown = new Set()
4
4
 
5
5
  function log(type, messages, key) {
6
- if (process.env.JEST_WORKER_ID !== undefined) return
6
+ if (typeof process !== 'undefined' && process.env.JEST_WORKER_ID) return
7
7
 
8
8
  if (key && alreadyShown.has(key)) return
9
9
  if (key) alreadyShown.add(key)
@@ -22,5 +22,9 @@ export function formatClass(classPrefix, key) {
22
22
  return `-${classPrefix}${key}`
23
23
  }
24
24
 
25
+ if (key.startsWith('/')) {
26
+ return `${classPrefix}${key}`
27
+ }
28
+
25
29
  return `${classPrefix}-${key}`
26
30
  }
@@ -10,7 +10,15 @@ export default function (value) {
10
10
  return value.replace(/^[+-]?/, (sign) => (sign === '-' ? '' : '-'))
11
11
  }
12
12
 
13
- if (value.includes('var(') || value.includes('calc(')) {
14
- return `calc(${value} * -1)`
13
+ // What functions we support negating numeric values for
14
+ // var() isn't inherently a numeric function but we support it anyway
15
+ // The trigonometric functions are omitted because you'll need to use calc(…) with them _anyway_
16
+ // to produce generally useful results and that will be covered already
17
+ let numericFunctions = ['var', 'calc', 'min', 'max', 'clamp']
18
+
19
+ for (const fn of numericFunctions) {
20
+ if (value.includes(`${fn}(`)) {
21
+ return `calc(${value} * -1)`
22
+ }
15
23
  }
16
24
  }
@@ -56,9 +56,11 @@ export function normalizeConfig(config) {
56
56
 
57
57
  // When `config.content` is an object
58
58
  if (typeof config.content === 'object' && config.content !== null) {
59
- // Only `files`, `extract` and `transform` can exist in `config.content`
59
+ // Only `files`, `relative`, `extract`, and `transform` can exist in `config.content`
60
60
  if (
61
- Object.keys(config.content).some((key) => !['files', 'extract', 'transform'].includes(key))
61
+ Object.keys(config.content).some(
62
+ (key) => !['files', 'relative', 'extract', 'transform'].includes(key)
63
+ )
62
64
  ) {
63
65
  return false
64
66
  }
@@ -112,6 +114,14 @@ export function normalizeConfig(config) {
112
114
  ) {
113
115
  return false
114
116
  }
117
+
118
+ // `config.content.relative` is optional and can be a boolean
119
+ if (
120
+ typeof config.content.relative !== 'boolean' &&
121
+ typeof config.content.relative !== 'undefined'
122
+ ) {
123
+ return false
124
+ }
115
125
  }
116
126
 
117
127
  return true
@@ -140,6 +150,24 @@ export function normalizeConfig(config) {
140
150
  return []
141
151
  })()
142
152
 
153
+ // Normalize the `blocklist`
154
+ config.blocklist = (() => {
155
+ let { blocklist } = config
156
+
157
+ if (Array.isArray(blocklist)) {
158
+ if (blocklist.every((item) => typeof item === 'string')) {
159
+ return blocklist
160
+ }
161
+
162
+ log.warn('blocklist-invalid', [
163
+ 'The `blocklist` option must be an array of strings.',
164
+ 'https://tailwindcss.com/docs/content-configuration#discarding-classes',
165
+ ])
166
+ }
167
+
168
+ return []
169
+ })()
170
+
143
171
  // Normalize prefix option
144
172
  if (typeof config.prefix === 'function') {
145
173
  log.warn('prefix-function', [
@@ -154,6 +182,16 @@ export function normalizeConfig(config) {
154
182
 
155
183
  // Normalize the `content`
156
184
  config.content = {
185
+ relative: (() => {
186
+ let { content } = config
187
+
188
+ if (content?.relative) {
189
+ return content.relative
190
+ }
191
+
192
+ return config.future?.relativeContentPathsByDefault ?? false
193
+ })(),
194
+
157
195
  files: (() => {
158
196
  let { content, purge } = config
159
197
 
@@ -258,13 +296,5 @@ export function normalizeConfig(config) {
258
296
  }
259
297
  }
260
298
 
261
- if (config.content.files.length === 0) {
262
- log.warn('content-problems', [
263
- 'The `content` option in your Tailwind CSS configuration is missing or empty.',
264
- 'Configure your content sources or your generated CSS will be missing styles.',
265
- 'https://tailwindcss.com/docs/content-configuration',
266
- ])
267
- }
268
-
269
299
  return config
270
300
  }