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

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 (176) hide show
  1. package/CHANGELOG.md +379 -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 +18 -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 +107 -611
  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 +28 -10
  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/{nesting → lib/postcss-plugins/nesting}/README.md +2 -2
  46. package/lib/postcss-plugins/nesting/index.js +19 -0
  47. package/lib/postcss-plugins/nesting/plugin.js +87 -0
  48. package/lib/processTailwindFeatures.js +35 -25
  49. package/lib/public/colors.js +247 -245
  50. package/lib/public/create-plugin.js +6 -4
  51. package/lib/public/default-config.js +7 -5
  52. package/lib/public/default-theme.js +7 -5
  53. package/lib/public/resolve-config.js +8 -5
  54. package/lib/util/bigSign.js +4 -1
  55. package/lib/util/buildMediaQuery.js +11 -6
  56. package/lib/util/cloneDeep.js +7 -6
  57. package/lib/util/cloneNodes.js +21 -3
  58. package/lib/util/color.js +53 -54
  59. package/lib/util/configurePlugins.js +5 -2
  60. package/lib/util/createPlugin.js +6 -6
  61. package/lib/util/createUtilityPlugin.js +12 -14
  62. package/lib/util/dataTypes.js +119 -110
  63. package/lib/util/defaults.js +4 -1
  64. package/lib/util/escapeClassName.js +7 -4
  65. package/lib/util/escapeCommas.js +5 -2
  66. package/lib/util/flattenColorPalette.js +9 -12
  67. package/lib/util/formatVariantSelector.js +184 -85
  68. package/lib/util/getAllConfigs.js +27 -8
  69. package/lib/util/hashConfig.js +6 -3
  70. package/lib/util/isKeyframeRule.js +5 -2
  71. package/lib/util/isPlainObject.js +5 -2
  72. package/lib/util/{isValidArbitraryValue.js → isSyntacticallyValidPropertyValue.js} +23 -15
  73. package/lib/util/log.js +20 -14
  74. package/lib/util/nameClass.js +20 -9
  75. package/lib/util/negateValue.js +23 -8
  76. package/lib/util/normalizeConfig.js +116 -72
  77. package/lib/util/normalizeScreens.js +120 -11
  78. package/lib/util/parseAnimationValue.js +42 -40
  79. package/lib/util/parseBoxShadowValue.js +30 -23
  80. package/lib/util/parseDependency.js +38 -56
  81. package/lib/util/parseGlob.js +34 -0
  82. package/lib/util/parseObjectStyles.js +11 -8
  83. package/lib/util/pluginUtils.js +147 -50
  84. package/lib/util/prefixSelector.js +10 -8
  85. package/lib/util/removeAlphaVariables.js +29 -0
  86. package/lib/util/resolveConfig.js +97 -85
  87. package/lib/util/resolveConfigPath.js +11 -9
  88. package/lib/util/responsive.js +8 -5
  89. package/lib/util/splitAtTopLevelOnly.js +43 -0
  90. package/lib/util/tap.js +4 -1
  91. package/lib/util/toColorValue.js +5 -3
  92. package/lib/util/toPath.js +20 -4
  93. package/lib/util/transformThemeValue.js +37 -29
  94. package/lib/util/validateConfig.js +24 -0
  95. package/lib/util/validateFormalSyntax.js +24 -0
  96. package/lib/util/withAlphaVariable.js +23 -15
  97. package/nesting/index.js +2 -12
  98. package/package.json +47 -42
  99. package/peers/index.js +11381 -7950
  100. package/plugin.d.ts +11 -0
  101. package/resolveConfig.d.ts +12 -0
  102. package/scripts/generate-types.js +105 -0
  103. package/scripts/release-channel.js +18 -0
  104. package/scripts/release-notes.js +21 -0
  105. package/scripts/type-utils.js +27 -0
  106. package/src/cli/build/deps.js +56 -0
  107. package/src/cli/build/index.js +49 -0
  108. package/src/cli/build/plugin.js +439 -0
  109. package/src/cli/build/utils.js +76 -0
  110. package/src/cli/build/watching.js +227 -0
  111. package/src/cli/help/index.js +70 -0
  112. package/src/cli/index.js +3 -0
  113. package/src/cli/init/index.js +50 -0
  114. package/src/cli/shared.js +6 -0
  115. package/src/cli-peer-dependencies.js +7 -1
  116. package/src/cli.js +50 -629
  117. package/src/corePluginList.js +1 -1
  118. package/src/corePlugins.js +532 -217
  119. package/src/css/preflight.css +5 -5
  120. package/src/featureFlags.js +15 -9
  121. package/src/index.js +20 -1
  122. package/src/lib/cacheInvalidation.js +52 -0
  123. package/src/lib/collapseAdjacentRules.js +21 -2
  124. package/src/lib/content.js +212 -0
  125. package/src/lib/defaultExtractor.js +196 -33
  126. package/src/lib/evaluateTailwindFunctions.js +78 -7
  127. package/src/lib/expandApplyAtRules.js +482 -183
  128. package/src/lib/expandTailwindAtRules.js +106 -85
  129. package/src/lib/findAtConfigPath.js +48 -0
  130. package/src/lib/generateRules.js +418 -129
  131. package/src/lib/normalizeTailwindDirectives.js +1 -0
  132. package/src/lib/offsets.js +270 -0
  133. package/src/lib/partitionApplyAtRules.js +52 -0
  134. package/src/lib/regex.js +74 -0
  135. package/src/lib/resolveDefaultsAtRules.js +51 -30
  136. package/src/lib/setupContextUtils.js +556 -208
  137. package/src/lib/setupTrackingContext.js +11 -48
  138. package/src/lib/sharedState.js +5 -0
  139. package/src/postcss-plugins/nesting/README.md +42 -0
  140. package/src/postcss-plugins/nesting/index.js +13 -0
  141. package/src/postcss-plugins/nesting/plugin.js +80 -0
  142. package/src/processTailwindFeatures.js +8 -0
  143. package/src/util/buildMediaQuery.js +5 -3
  144. package/src/util/cloneNodes.js +19 -2
  145. package/src/util/color.js +25 -21
  146. package/src/util/dataTypes.js +29 -21
  147. package/src/util/formatVariantSelector.js +184 -61
  148. package/src/util/getAllConfigs.js +19 -0
  149. package/src/util/{isValidArbitraryValue.js → isSyntacticallyValidPropertyValue.js} +1 -1
  150. package/src/util/log.js +8 -8
  151. package/src/util/nameClass.js +4 -0
  152. package/src/util/negateValue.js +11 -3
  153. package/src/util/normalizeConfig.js +44 -6
  154. package/src/util/normalizeScreens.js +99 -4
  155. package/src/util/parseBoxShadowValue.js +4 -3
  156. package/src/util/parseDependency.js +37 -42
  157. package/src/util/parseGlob.js +24 -0
  158. package/src/util/pluginUtils.js +132 -10
  159. package/src/util/prefixSelector.js +7 -5
  160. package/src/util/removeAlphaVariables.js +24 -0
  161. package/src/util/resolveConfig.js +70 -32
  162. package/src/util/splitAtTopLevelOnly.js +45 -0
  163. package/src/util/toPath.js +1 -1
  164. package/src/util/transformThemeValue.js +13 -3
  165. package/src/util/validateConfig.js +13 -0
  166. package/src/util/validateFormalSyntax.js +34 -0
  167. package/src/util/withAlphaVariable.js +1 -1
  168. package/stubs/defaultConfig.stub.js +23 -20
  169. package/stubs/simpleConfig.stub.js +1 -0
  170. package/types/config.d.ts +362 -0
  171. package/types/generated/.gitkeep +0 -0
  172. package/types/generated/colors.d.ts +276 -0
  173. package/types/generated/corePluginList.d.ts +1 -0
  174. package/types/generated/default-theme.d.ts +342 -0
  175. package/types/index.d.ts +7 -0
  176. package/nesting/plugin.js +0 -41
@@ -29,25 +29,117 @@ export function formatVariantSelector(current, ...others) {
29
29
  return current
30
30
  }
31
31
 
32
- export function finalizeSelector(format, { selector, candidate, context }) {
33
- let separator = context?.tailwindConfig?.separator ?? ':'
32
+ /**
33
+ * Given any node in a selector this gets the "simple" selector it's a part of
34
+ * A simple selector is just a list of nodes without any combinators
35
+ * Technically :is(), :not(), :has(), etc… can have combinators but those are nested
36
+ * inside the relevant node and won't be picked up so they're fine to ignore
37
+ *
38
+ * @param {import('postcss-selector-parser').Node} node
39
+ * @returns {import('postcss-selector-parser').Node[]}
40
+ **/
41
+ function simpleSelectorForNode(node) {
42
+ /** @type {import('postcss-selector-parser').Node[]} */
43
+ let nodes = []
44
+
45
+ // Walk backwards until we hit a combinator node (or the start)
46
+ while (node.prev() && node.prev().type !== 'combinator') {
47
+ node = node.prev()
48
+ }
34
49
 
35
- // Split by the separator, but ignore the separator inside square brackets:
36
- //
37
- // E.g.: dark:lg:hover:[paint-order:markers]
38
- // ┬ ┬ ┬ ┬
39
- // │ │ │ ╰── We will not split here
40
- // ╰──┴─────┴─────────────── We will split here
41
- //
42
- let splitter = new RegExp(`\\${separator}(?![^[]*\\])`)
43
- let base = candidate.split(splitter).pop()
50
+ // Now record all non-combinator nodes until we hit one (or the end)
51
+ while (node && node.type !== 'combinator') {
52
+ nodes.push(node)
53
+ node = node.next()
54
+ }
55
+
56
+ return nodes
57
+ }
58
+
59
+ /**
60
+ * Resorts the nodes in a selector to ensure they're in the correct order
61
+ * Tags go before classes, and pseudo classes go after classes
62
+ *
63
+ * @param {import('postcss-selector-parser').Selector} sel
64
+ * @returns {import('postcss-selector-parser').Selector}
65
+ **/
66
+ function resortSelector(sel) {
67
+ sel.sort((a, b) => {
68
+ if (a.type === 'tag' && b.type === 'class') {
69
+ return -1
70
+ } else if (a.type === 'class' && b.type === 'tag') {
71
+ return 1
72
+ } else if (a.type === 'class' && b.type === 'pseudo' && b.value.startsWith('::')) {
73
+ return -1
74
+ } else if (a.type === 'pseudo' && a.value.startsWith('::') && b.type === 'class') {
75
+ return 1
76
+ }
77
+
78
+ return sel.index(a) - sel.index(b)
79
+ })
80
+
81
+ return sel
82
+ }
83
+
84
+ function eliminateIrrelevantSelectors(sel, base) {
85
+ let hasClassesMatchingCandidate = false
44
86
 
45
- if (context?.tailwindConfig?.prefix) {
87
+ sel.walk((child) => {
88
+ if (child.type === 'class' && child.value === base) {
89
+ hasClassesMatchingCandidate = true
90
+ return false // Stop walking
91
+ }
92
+ })
93
+
94
+ if (!hasClassesMatchingCandidate) {
95
+ sel.remove()
96
+ }
97
+
98
+ // We do NOT recursively eliminate sub selectors that don't have the base class
99
+ // as this is NOT a safe operation. For example, if we have:
100
+ // `.space-x-2 > :not([hidden]) ~ :not([hidden])`
101
+ // We cannot remove the [hidden] from the :not() because it would change the
102
+ // meaning of the selector.
103
+
104
+ // TODO: Can we do this for :matches, :is, and :where?
105
+ }
106
+
107
+ export function finalizeSelector(
108
+ format,
109
+ {
110
+ selector,
111
+ candidate,
112
+ context,
113
+ isArbitraryVariant,
114
+
115
+ // Split by the separator, but ignore the separator inside square brackets:
116
+ //
117
+ // E.g.: dark:lg:hover:[paint-order:markers]
118
+ // ┬ ┬ ┬ ┬
119
+ // │ │ │ ╰── We will not split here
120
+ // ╰──┴─────┴─────────────── We will split here
121
+ //
122
+ base = candidate
123
+ .split(new RegExp(`\\${context?.tailwindConfig?.separator ?? ':'}(?![^[]*\\])`))
124
+ .pop(),
125
+ }
126
+ ) {
127
+ let ast = selectorParser().astSync(selector)
128
+
129
+ // We explicitly DO NOT prefix classes in arbitrary variants
130
+ if (context?.tailwindConfig?.prefix && !isArbitraryVariant) {
46
131
  format = prefixSelector(context.tailwindConfig.prefix, format)
47
132
  }
48
133
 
49
134
  format = format.replace(PARENT, `.${escapeClassName(candidate)}`)
50
135
 
136
+ let formatAst = selectorParser().astSync(format)
137
+
138
+ // Remove extraneous selectors that do not include the base class/candidate being matched against
139
+ // For example if we have a utility defined `.a, .b { color: red}`
140
+ // And the formatted variant is sm:b then we want the final selector to be `.sm\:b` and not `.a, .sm\:b`
141
+ ast.each((sel) => eliminateIrrelevantSelectors(sel, base))
142
+
51
143
  // Normalize escaped classes, e.g.:
52
144
  //
53
145
  // The idea would be to replace the escaped `base` in the selector with the
@@ -59,65 +151,96 @@ export function finalizeSelector(format, { selector, candidate, context }) {
59
151
  // base in selector: bg-\\[rgb\\(255\\,0\\,0\\)\\]
60
152
  // escaped base: bg-\\[rgb\\(255\\2c 0\\2c 0\\)\\]
61
153
  //
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
- }
154
+ ast.walkClasses((node) => {
155
+ if (node.raws && node.value.includes(base)) {
156
+ node.raws.value = escapeClassName(unescape(node.raws.value))
157
+ }
158
+ })
67
159
 
68
- return node
69
- })
70
- }).processSync(selector)
160
+ let simpleStart = selectorParser.comment({ value: '/*__simple__*/' })
161
+ let simpleEnd = selectorParser.comment({ value: '/*__simple__*/' })
71
162
 
72
163
  // We can safely replace the escaped base now, since the `base` section is
73
164
  // now in a normalized escaped value.
74
- selector = selector.replace(`.${escapeClassName(base)}`, format)
165
+ ast.walkClasses((node) => {
166
+ if (node.value !== base) {
167
+ return
168
+ }
75
169
 
76
- // 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
85
- })
86
-
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
170
+ let parent = node.parent
171
+ let formatNodes = formatAst.nodes[0].nodes
172
+
173
+ // Perf optimization: if the parent is a single class we can just replace it and be done
174
+ if (parent.nodes.length === 1) {
175
+ node.replaceWith(...formatNodes)
176
+ return
177
+ }
178
+
179
+ let simpleSelector = simpleSelectorForNode(node)
180
+ parent.insertBefore(simpleSelector[0], simpleStart)
181
+ parent.insertAfter(simpleSelector[simpleSelector.length - 1], simpleEnd)
182
+
183
+ for (let child of formatNodes) {
184
+ parent.insertBefore(simpleSelector[0], child)
185
+ }
186
+
187
+ node.remove()
188
+
189
+ // Re-sort the simple selector to ensure it's in the correct order
190
+ simpleSelector = simpleSelectorForNode(simpleStart)
191
+ let firstNode = parent.index(simpleStart)
192
+
193
+ parent.nodes.splice(
194
+ firstNode,
195
+ simpleSelector.length,
196
+ ...resortSelector(selectorParser.selector({ nodes: simpleSelector })).nodes
197
+ )
198
+
199
+ simpleStart.remove()
200
+ simpleEnd.remove()
201
+ })
202
+
203
+ // This will make sure to move pseudo's to the correct spot (the end for
204
+ // pseudo elements) because otherwise the selector will never work
205
+ // anyway.
206
+ //
207
+ // E.g.:
208
+ // - `before:hover:text-center` would result in `.before\:hover\:text-center:hover::before`
209
+ // - `hover:before:text-center` would result in `.hover\:before\:text-center:hover::before`
210
+ //
211
+ // `::before:hover` doesn't work, which means that we can make it work for you by flipping the order.
212
+ function collectPseudoElements(selector) {
213
+ let nodes = []
214
+
215
+ for (let node of selector.nodes) {
216
+ if (isPseudoElement(node)) {
217
+ nodes.push(node)
218
+ selector.removeChild(node)
111
219
  }
112
220
 
113
- let pseudoElements = collectPseudoElements(selector)
114
- if (pseudoElements.length > 0) {
115
- selector.nodes.push(pseudoElements.sort(sortSelector))
221
+ if (node?.nodes) {
222
+ nodes.push(...collectPseudoElements(node))
116
223
  }
224
+ }
225
+
226
+ return nodes
227
+ }
117
228
 
118
- return selector
229
+ // Remove unnecessary pseudo selectors that we used as placeholders
230
+ ast.each((selector) => {
231
+ selector.walkPseudos((p) => {
232
+ if (selectorFunctions.has(p.value)) {
233
+ p.replaceWith(p.nodes)
234
+ }
119
235
  })
120
- }).processSync(selector)
236
+
237
+ let pseudoElements = collectPseudoElements(selector)
238
+ if (pseudoElements.length > 0) {
239
+ selector.nodes.push(pseudoElements.sort(sortSelector))
240
+ }
241
+ })
242
+
243
+ return ast.toString()
121
244
  }
122
245
 
123
246
  // Note: As a rule, double colons (::) should be used instead of a single colon
@@ -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
@@ -1,29 +1,29 @@
1
- import chalk from 'chalk'
1
+ import colors from 'picocolors'
2
2
 
3
3
  let alreadyShown = new Set()
4
4
 
5
- function log(chalk, messages, key) {
6
- if (process.env.JEST_WORKER_ID !== undefined) return
5
+ function log(type, messages, key) {
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)
10
10
 
11
11
  console.warn('')
12
- messages.forEach((message) => console.warn(chalk, '-', message))
12
+ messages.forEach((message) => console.warn(type, '-', message))
13
13
  }
14
14
 
15
15
  export function dim(input) {
16
- return chalk.dim(input)
16
+ return colors.dim(input)
17
17
  }
18
18
 
19
19
  export default {
20
20
  info(key, messages) {
21
- log(chalk.bold.cyan('info'), ...(Array.isArray(key) ? [key] : [messages, key]))
21
+ log(colors.bold(colors.cyan('info')), ...(Array.isArray(key) ? [key] : [messages, key]))
22
22
  },
23
23
  warn(key, messages) {
24
- log(chalk.bold.yellow('warn'), ...(Array.isArray(key) ? [key] : [messages, key]))
24
+ log(colors.bold(colors.yellow('warn')), ...(Array.isArray(key) ? [key] : [messages, key]))
25
25
  },
26
26
  risk(key, messages) {
27
- log(chalk.bold.magenta('risk'), ...(Array.isArray(key) ? [key] : [messages, key]))
27
+ log(colors.bold(colors.magenta('risk')), ...(Array.isArray(key) ? [key] : [messages, key]))
28
28
  },
29
29
  }
@@ -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
  }
@@ -1,4 +1,4 @@
1
- export default function (value) {
1
+ export default function negateValue(value) {
2
2
  value = `${value}`
3
3
 
4
4
  if (value === '0') {
@@ -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
@@ -124,7 +134,7 @@ export function normalizeConfig(config) {
124
134
  log.warn('purge-deprecation', [
125
135
  'The `purge`/`content` options have changed in Tailwind CSS v3.0.',
126
136
  'Update your configuration file to eliminate this warning.',
127
- // TODO: Add https://tw.wtf/purge-deprecation
137
+ 'https://tailwindcss.com/docs/upgrade-guide#configure-content-sources',
128
138
  ])
129
139
  }
130
140
 
@@ -140,12 +150,30 @@ 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', [
146
174
  'As of Tailwind CSS v3.0, `prefix` cannot be a function.',
147
175
  'Update `prefix` in your configuration to be a string to eliminate this warning.',
148
- // TODO: Add https://tw.wtf/prefix-function
176
+ 'https://tailwindcss.com/docs/upgrade-guide#prefix-cannot-be-a-function',
149
177
  ])
150
178
  config.prefix = ''
151
179
  } else {
@@ -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
 
@@ -250,8 +288,8 @@ export function normalizeConfig(config) {
250
288
  for (let file of config.content.files) {
251
289
  if (typeof file === 'string' && /{([^,]*?)}/g.test(file)) {
252
290
  log.warn('invalid-glob-braces', [
253
- `The glob pattern ${dim(file)} in your config is invalid.`,
254
- ` Update it to ${dim(file.replace(/{([^,]*?)}/g, '$1'))} to silence this warning.`,
291
+ `The glob pattern ${dim(file)} in your Tailwind CSS configuration is invalid.`,
292
+ `Update it to ${dim(file.replace(/{([^,]*?)}/g, '$1'))} to silence this warning.`,
255
293
  // TODO: Add https://tw.wtf/invalid-glob-braces
256
294
  ])
257
295
  break
@@ -1,3 +1,17 @@
1
+ /**
2
+ * @typedef {object} ScreenValue
3
+ * @property {number|undefined} min
4
+ * @property {number|undefined} max
5
+ * @property {string|undefined} raw
6
+ */
7
+
8
+ /**
9
+ * @typedef {object} Screen
10
+ * @property {string} name
11
+ * @property {boolean} not
12
+ * @property {ScreenValue[]} values
13
+ */
14
+
1
15
  /**
2
16
  * A function that normalizes the various forms that the screens object can be
3
17
  * provided in.
@@ -10,6 +24,8 @@
10
24
  *
11
25
  * Output(s):
12
26
  * - [{ name: 'sm', values: [{ min: '100px', max: '200px' }] }] // List of objects, that contains multiple values
27
+ *
28
+ * @returns {Screen[]}
13
29
  */
14
30
  export function normalizeScreens(screens, root = true) {
15
31
  if (Array.isArray(screens)) {
@@ -19,27 +35,106 @@ export function normalizeScreens(screens, root = true) {
19
35
  }
20
36
 
21
37
  if (typeof screen === 'string') {
22
- return { name: screen.toString(), values: [{ min: screen, max: undefined }] }
38
+ return { name: screen.toString(), not: false, values: [{ min: screen, max: undefined }] }
23
39
  }
24
40
 
25
41
  let [name, options] = screen
26
42
  name = name.toString()
27
43
 
28
44
  if (typeof options === 'string') {
29
- return { name, values: [{ min: options, max: undefined }] }
45
+ return { name, not: false, values: [{ min: options, max: undefined }] }
30
46
  }
31
47
 
32
48
  if (Array.isArray(options)) {
33
- return { name, values: options.map((option) => resolveValue(option)) }
49
+ return { name, not: false, values: options.map((option) => resolveValue(option)) }
34
50
  }
35
51
 
36
- return { name, values: [resolveValue(options)] }
52
+ return { name, not: false, values: [resolveValue(options)] }
37
53
  })
38
54
  }
39
55
 
40
56
  return normalizeScreens(Object.entries(screens ?? {}), false)
41
57
  }
42
58
 
59
+ /**
60
+ * @param {Screen} screen
61
+ * @returns {{result: false, reason: string} | {result: true, reason: null}}
62
+ */
63
+ export function isScreenSortable(screen) {
64
+ if (screen.values.length !== 1) {
65
+ return { result: false, reason: 'multiple-values' }
66
+ } else if (screen.values[0].raw !== undefined) {
67
+ return { result: false, reason: 'raw-values' }
68
+ } else if (screen.values[0].min !== undefined && screen.values[0].max !== undefined) {
69
+ return { result: false, reason: 'min-and-max' }
70
+ }
71
+
72
+ return { result: true, reason: null }
73
+ }
74
+
75
+ /**
76
+ * @param {'min' | 'max'} type
77
+ * @param {Screen | 'string'} a
78
+ * @param {Screen | 'string'} z
79
+ * @returns {number}
80
+ */
81
+ export function compareScreens(type, a, z) {
82
+ let aScreen = toScreen(a, type)
83
+ let zScreen = toScreen(z, type)
84
+
85
+ let aSorting = isScreenSortable(aScreen)
86
+ let bSorting = isScreenSortable(zScreen)
87
+
88
+ // These cases should never happen and indicate a bug in Tailwind CSS itself
89
+ if (aSorting.reason === 'multiple-values' || bSorting.reason === 'multiple-values') {
90
+ throw new Error(
91
+ 'Attempted to sort a screen with multiple values. This should never happen. Please open a bug report.'
92
+ )
93
+ } else if (aSorting.reason === 'raw-values' || bSorting.reason === 'raw-values') {
94
+ throw new Error(
95
+ 'Attempted to sort a screen with raw values. This should never happen. Please open a bug report.'
96
+ )
97
+ } else if (aSorting.reason === 'min-and-max' || bSorting.reason === 'min-and-max') {
98
+ throw new Error(
99
+ 'Attempted to sort a screen with both min and max values. This should never happen. Please open a bug report.'
100
+ )
101
+ }
102
+
103
+ // Let the sorting begin
104
+ let { min: aMin, max: aMax } = aScreen.values[0]
105
+ let { min: zMin, max: zMax } = zScreen.values[0]
106
+
107
+ // Negating screens flip their behavior. Basically `not min-width` is `max-width`
108
+ if (a.not) [aMin, aMax] = [aMax, aMin]
109
+ if (z.not) [zMin, zMax] = [zMax, zMin]
110
+
111
+ aMin = aMin === undefined ? aMin : parseFloat(aMin)
112
+ aMax = aMax === undefined ? aMax : parseFloat(aMax)
113
+ zMin = zMin === undefined ? zMin : parseFloat(zMin)
114
+ zMax = zMax === undefined ? zMax : parseFloat(zMax)
115
+
116
+ let [aValue, zValue] = type === 'min' ? [aMin, zMin] : [zMax, aMax]
117
+
118
+ return aValue - zValue
119
+ }
120
+
121
+ /**
122
+ *
123
+ * @param {PartialScreen> | string} value
124
+ * @param {'min' | 'max'} type
125
+ * @returns {Screen}
126
+ */
127
+ export function toScreen(value, type) {
128
+ if (typeof value === 'object') {
129
+ return value
130
+ }
131
+
132
+ return {
133
+ name: 'arbitrary-screen',
134
+ values: [{ [type]: value }],
135
+ }
136
+ }
137
+
43
138
  function resolveValue({ 'min-width': _minWidth, min = _minWidth, max, raw } = {}) {
44
139
  return { min, max, raw }
45
140
  }
@@ -1,10 +1,11 @@
1
+ import { splitAtTopLevelOnly } from './splitAtTopLevelOnly'
2
+
1
3
  let KEYWORDS = new Set(['inset', 'inherit', 'initial', 'revert', 'unset'])
2
- let COMMA = /\,(?![^(]*\))/g // Comma separator that is not located between brackets. E.g.: `cubiz-bezier(a, b, c)` these don't count.
3
4
  let SPACE = /\ +(?![^(]*\))/g // Similar to the one above, but with spaces instead.
4
- let LENGTH = /^-?(\d+)(.*?)$/g
5
+ let LENGTH = /^-?(\d+|\.\d+)(.*?)$/g
5
6
 
6
7
  export function parseBoxShadowValue(input) {
7
- let shadows = input.split(COMMA)
8
+ let shadows = splitAtTopLevelOnly(input, ',')
8
9
  return shadows.map((shadow) => {
9
10
  let value = shadow.trim()
10
11
  let result = { raw: value }