tailwindcss 3.0.0-alpha.1 → 3.0.0-alpha.2

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 (60) hide show
  1. package/CHANGELOG.md +67 -1
  2. package/lib/cli.js +66 -62
  3. package/lib/constants.js +1 -1
  4. package/lib/corePluginList.js +7 -1
  5. package/lib/corePlugins.js +264 -203
  6. package/lib/css/preflight.css +12 -0
  7. package/lib/featureFlags.js +10 -7
  8. package/lib/lib/collapseDuplicateDeclarations.js +29 -0
  9. package/lib/lib/evaluateTailwindFunctions.js +3 -3
  10. package/lib/lib/expandApplyAtRules.js +7 -7
  11. package/lib/lib/expandTailwindAtRules.js +2 -1
  12. package/lib/lib/generateRules.js +115 -19
  13. package/lib/lib/resolveDefaultsAtRules.js +44 -47
  14. package/lib/lib/setupContextUtils.js +72 -15
  15. package/lib/lib/setupWatchingContext.js +5 -1
  16. package/lib/lib/sharedState.js +2 -2
  17. package/lib/processTailwindFeatures.js +4 -0
  18. package/lib/util/createUtilityPlugin.js +5 -5
  19. package/lib/util/dataTypes.js +24 -4
  20. package/lib/util/formatVariantSelector.js +102 -0
  21. package/lib/util/nameClass.js +1 -1
  22. package/lib/util/negateValue.js +3 -1
  23. package/lib/util/normalizeConfig.js +22 -8
  24. package/lib/util/parseBoxShadowValue.js +77 -0
  25. package/lib/util/pluginUtils.js +62 -158
  26. package/lib/util/prefixSelector.js +1 -3
  27. package/lib/util/resolveConfig.js +13 -9
  28. package/lib/util/transformThemeValue.js +23 -13
  29. package/package.json +11 -11
  30. package/peers/index.js +873 -2505
  31. package/src/cli.js +9 -2
  32. package/src/corePluginList.js +1 -1
  33. package/src/corePlugins.js +282 -348
  34. package/src/css/preflight.css +12 -0
  35. package/src/featureFlags.js +10 -4
  36. package/src/lib/collapseDuplicateDeclarations.js +28 -0
  37. package/src/lib/expandTailwindAtRules.js +3 -2
  38. package/src/lib/generateRules.js +121 -11
  39. package/src/lib/resolveDefaultsAtRules.js +39 -43
  40. package/src/lib/setupContextUtils.js +71 -9
  41. package/src/lib/setupWatchingContext.js +7 -0
  42. package/src/lib/sharedState.js +1 -1
  43. package/src/processTailwindFeatures.js +5 -0
  44. package/src/util/createUtilityPlugin.js +2 -2
  45. package/src/util/dataTypes.js +32 -5
  46. package/src/util/formatVariantSelector.js +105 -0
  47. package/src/util/nameClass.js +1 -1
  48. package/src/util/negateValue.js +4 -2
  49. package/src/util/normalizeConfig.js +17 -1
  50. package/src/util/parseBoxShadowValue.js +71 -0
  51. package/src/util/pluginUtils.js +50 -146
  52. package/src/util/prefixSelector.js +1 -4
  53. package/src/util/resolveConfig.js +7 -1
  54. package/src/util/transformThemeValue.js +22 -7
  55. package/stubs/defaultConfig.stub.js +101 -58
  56. package/peers/.svgo.yml +0 -75
  57. package/peers/orders/concentric-css.json +0 -299
  58. package/peers/orders/smacss.json +0 -299
  59. package/peers/orders/source.json +0 -295
  60. package/src/.DS_Store +0 -0
@@ -12,6 +12,11 @@
12
12
  border-color: currentColor; /* 2 */
13
13
  }
14
14
 
15
+ ::before,
16
+ ::after {
17
+ --tw-content: '';
18
+ }
19
+
15
20
  /*
16
21
  1. Use a consistent sensible line-height in all browsers.
17
22
  2. Prevent adjustments of font size after orientation changes in iOS.
@@ -317,6 +322,13 @@ button,
317
322
  cursor: pointer;
318
323
  }
319
324
 
325
+ /*
326
+ Make sure disabled buttons don't get the pointer cursor.
327
+ */
328
+ :disabled {
329
+ cursor: default;
330
+ }
331
+
320
332
  /*
321
333
  1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
322
334
  2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
@@ -1,18 +1,24 @@
1
1
  import chalk from 'chalk'
2
2
  import log from './util/log'
3
3
 
4
- const featureFlags = {
4
+ let defaults = {
5
+ optimizeUniversalDefaults: true,
6
+ }
7
+
8
+ let featureFlags = {
5
9
  future: [],
6
10
  experimental: ['optimizeUniversalDefaults'],
7
11
  }
8
12
 
9
13
  export function flagEnabled(config, flag) {
10
14
  if (featureFlags.future.includes(flag)) {
11
- return config.future === 'all' || (config?.future?.[flag] ?? false)
15
+ return config.future === 'all' || (config?.future?.[flag] ?? defaults[flag] ?? false)
12
16
  }
13
17
 
14
18
  if (featureFlags.experimental.includes(flag)) {
15
- return config.experimental === 'all' || (config?.experimental?.[flag] ?? false)
19
+ return (
20
+ config.experimental === 'all' || (config?.experimental?.[flag] ?? defaults[flag] ?? false)
21
+ )
16
22
  }
17
23
 
18
24
  return false
@@ -34,7 +40,7 @@ export function issueFlagNotices(config) {
34
40
  }
35
41
 
36
42
  if (experimentalFlagsEnabled(config).length > 0) {
37
- const changes = experimentalFlagsEnabled(config)
43
+ let changes = experimentalFlagsEnabled(config)
38
44
  .map((s) => chalk.yellow(s))
39
45
  .join(', ')
40
46
 
@@ -0,0 +1,28 @@
1
+ export default function collapseDuplicateDeclarations() {
2
+ return (root) => {
3
+ root.walkRules((node) => {
4
+ let seen = new Map()
5
+ let droppable = new Set([])
6
+
7
+ node.walkDecls((decl) => {
8
+ // This could happen if we have nested selectors. In that case the
9
+ // parent will loop over all its declarations but also the declarations
10
+ // of nested rules. With this we ensure that we are shallowly checking
11
+ // declarations.
12
+ if (decl.parent !== node) {
13
+ return
14
+ }
15
+
16
+ if (seen.has(decl.prop)) {
17
+ droppable.add(seen.get(decl.prop))
18
+ }
19
+
20
+ seen.set(decl.prop, decl)
21
+ })
22
+
23
+ for (let decl of droppable) {
24
+ decl.remove()
25
+ }
26
+ })
27
+ }
28
+ }
@@ -11,10 +11,12 @@ const PATTERNS = [
11
11
  /([^<>"'`\s]*\[\w*"[^"`\s]*"?\])/.source, // font-["some_font",sans-serif]
12
12
  /([^<>"'`\s]*\[\w*\('[^"'`\s]*'\)\])/.source, // bg-[url('...')]
13
13
  /([^<>"'`\s]*\[\w*\("[^"'`\s]*"\)\])/.source, // bg-[url("...")]
14
+ /([^<>"'`\s]*\[\w*\('[^"`\s]*'\)\])/.source, // bg-[url('...'),url('...')]
15
+ /([^<>"'`\s]*\[\w*\("[^'`\s]*"\)\])/.source, // bg-[url("..."),url("...")]
14
16
  /([^<>"'`\s]*\['[^"'`\s]*'\])/.source, // `content-['hello']` but not `content-['hello']']`
15
17
  /([^<>"'`\s]*\["[^"'`\s]*"\])/.source, // `content-["hello"]` but not `content-["hello"]"]`
16
18
  /([^<>"'`\s]*\[[^"'`\s]+\][^<>"'`\s]*)/.source, // `fill-[#bada55]`, `fill-[#bada55]/50`
17
- /([^<>"'`\s]*[^"'`\s:])/.source, // `px-1.5`, `uppercase` but not `uppercase:`].join('|')
19
+ /([^<>"'`\s]*[^"'`\s:])/.source, // `px-1.5`, `uppercase` but not `uppercase:`
18
20
  ].join('|')
19
21
  const BROAD_MATCH_GLOBAL_REGEXP = new RegExp(PATTERNS, 'g')
20
22
  const INNER_MATCH_GLOBAL_REGEXP = /[^<>"'`\s.(){}[\]#=%]*[^<>"'`\s.(){}[\]#=%:]/g
@@ -235,7 +237,6 @@ export default function expandTailwindAtRules(context) {
235
237
  if (env.DEBUG) {
236
238
  console.log('Potential classes: ', candidates.size)
237
239
  console.log('Active contexts: ', sharedState.contextSourcesMap.size)
238
- console.log('Content match entries', contentMatchCache.size)
239
240
  }
240
241
 
241
242
  // Clear the cache for the changed files
@@ -5,6 +5,7 @@ import isPlainObject from '../util/isPlainObject'
5
5
  import prefixSelector from '../util/prefixSelector'
6
6
  import { updateAllClasses } from '../util/pluginUtils'
7
7
  import log from '../util/log'
8
+ import { formatVariantSelector, finalizeSelector } from '../util/formatVariantSelector'
8
9
 
9
10
  let classNameParser = selectorParser((selectors) => {
10
11
  return selectors.first.filter(({ type }) => type === 'class').pop().value
@@ -112,7 +113,17 @@ function applyVariant(variant, matches, context) {
112
113
 
113
114
  for (let [variantSort, variantFunction] of variantFunctionTuples) {
114
115
  let clone = container.clone()
116
+ let collectedFormats = []
117
+
118
+ let originals = new Map()
119
+
120
+ function prepareBackup() {
121
+ if (originals.size > 0) return // Already prepared, chicken out
122
+ clone.walkRules((rule) => originals.set(rule, rule.selector))
123
+ }
124
+
115
125
  function modifySelectors(modifierFunction) {
126
+ prepareBackup()
116
127
  clone.each((rule) => {
117
128
  if (rule.type !== 'rule') {
118
129
  return
@@ -127,20 +138,84 @@ function applyVariant(variant, matches, context) {
127
138
  })
128
139
  })
129
140
  })
141
+
130
142
  return clone
131
143
  }
132
144
 
133
145
  let ruleWithVariant = variantFunction({
134
- container: clone,
146
+ // Public API
147
+ get container() {
148
+ prepareBackup()
149
+ return clone
150
+ },
135
151
  separator: context.tailwindConfig.separator,
136
152
  modifySelectors,
153
+
154
+ // Private API for now
155
+ wrap(wrapper) {
156
+ let nodes = clone.nodes
157
+ clone.removeAll()
158
+ wrapper.append(nodes)
159
+ clone.append(wrapper)
160
+ },
161
+ format(selectorFormat) {
162
+ collectedFormats.push(selectorFormat)
163
+ },
137
164
  })
138
165
 
166
+ if (typeof ruleWithVariant === 'string') {
167
+ collectedFormats.push(ruleWithVariant)
168
+ }
169
+
139
170
  if (ruleWithVariant === null) {
140
171
  continue
141
172
  }
142
173
 
143
- let withOffset = [{ ...meta, sort: variantSort | meta.sort }, clone.nodes[0]]
174
+ // We filled the `originals`, therefore we assume that somebody touched
175
+ // `container` or `modifySelectors`. Let's see if they did, so that we
176
+ // can restore the selectors, and collect the format strings.
177
+ if (originals.size > 0) {
178
+ clone.walkRules((rule) => {
179
+ if (!originals.has(rule)) return
180
+ let before = originals.get(rule)
181
+ if (before === rule.selector) return // No mutation happened
182
+
183
+ let modified = rule.selector
184
+
185
+ // Rebuild the base selector, this is what plugin authors would do
186
+ // as well. E.g.: `${variant}${separator}${className}`.
187
+ // However, plugin authors probably also prepend or append certain
188
+ // classes, pseudos, ids, ...
189
+ let rebuiltBase = selectorParser((selectors) => {
190
+ selectors.walkClasses((classNode) => {
191
+ classNode.value = `${variant}${context.tailwindConfig.separator}${classNode.value}`
192
+ })
193
+ }).processSync(before)
194
+
195
+ // Now that we know the original selector, the new selector, and
196
+ // the rebuild part in between, we can replace the part that plugin
197
+ // authors need to rebuild with `&`, and eventually store it in the
198
+ // collectedFormats. Similar to what `format('...')` would do.
199
+ //
200
+ // E.g.:
201
+ // variant: foo
202
+ // selector: .markdown > p
203
+ // modified (by plugin): .foo .foo\\:markdown > p
204
+ // rebuiltBase (internal): .foo\\:markdown > p
205
+ // format: .foo &
206
+ collectedFormats.push(modified.replace(rebuiltBase, '&'))
207
+ rule.selector = before
208
+ })
209
+ }
210
+
211
+ let withOffset = [
212
+ {
213
+ ...meta,
214
+ sort: variantSort | meta.sort,
215
+ collectedFormats: (meta.collectedFormats ?? []).concat(collectedFormats),
216
+ },
217
+ clone.nodes[0],
218
+ ]
144
219
  result.push(withOffset)
145
220
  }
146
221
  }
@@ -178,13 +253,18 @@ function* resolveMatchedPlugins(classCandidate, context) {
178
253
  let candidatePrefix = classCandidate
179
254
  let negative = false
180
255
 
181
- const twConfigPrefix = context.tailwindConfig.prefix || ''
256
+ const twConfigPrefix = context.tailwindConfig.prefix
257
+
182
258
  const twConfigPrefixLen = twConfigPrefix.length
183
259
  if (candidatePrefix[twConfigPrefixLen] === '-') {
184
260
  negative = true
185
261
  candidatePrefix = twConfigPrefix + candidatePrefix.slice(twConfigPrefixLen + 1)
186
262
  }
187
263
 
264
+ if (negative && context.candidateRuleMap.has(candidatePrefix)) {
265
+ yield [context.candidateRuleMap.get(candidatePrefix), '-DEFAULT']
266
+ }
267
+
188
268
  for (let [prefix, modifier] of candidatePermutations(candidatePrefix)) {
189
269
  if (context.candidateRuleMap.has(prefix)) {
190
270
  yield [context.candidateRuleMap.get(prefix), negative ? `-${modifier}` : modifier]
@@ -238,7 +318,7 @@ function* resolveMatches(candidate, context) {
238
318
  }
239
319
  }
240
320
  // Only process static plugins on exact matches
241
- else if (modifier === 'DEFAULT') {
321
+ else if (modifier === 'DEFAULT' || modifier === '-DEFAULT') {
242
322
  let ruleSet = plugin
243
323
  let [rules, options] = parseRules(ruleSet, context.postCssNodeCache)
244
324
  for (let rule of rules) {
@@ -319,6 +399,22 @@ function* resolveMatches(candidate, context) {
319
399
  }
320
400
 
321
401
  for (let match of matches) {
402
+ // Apply final format selector
403
+ if (match[0].collectedFormats) {
404
+ let finalFormat = formatVariantSelector('&', ...match[0].collectedFormats)
405
+ let container = postcss.root({ nodes: [match[1].clone()] })
406
+ container.walkRules((rule) => {
407
+ if (inKeyframes(rule)) return
408
+
409
+ rule.selector = finalizeSelector(finalFormat, {
410
+ selector: rule.selector,
411
+ candidate,
412
+ context,
413
+ })
414
+ })
415
+ match[1] = container.nodes[0]
416
+ }
417
+
322
418
  yield match
323
419
  }
324
420
  }
@@ -352,28 +448,42 @@ function generateRules(candidates, context) {
352
448
  allRules.push(matches)
353
449
  }
354
450
 
355
- return allRules.flat(1).map(([{ sort, layer, options }, rule]) => {
356
- if (options.respectImportant) {
357
- if (context.tailwindConfig.important === true) {
451
+ // Strategy based on `tailwindConfig.important`
452
+ let strategy = ((important) => {
453
+ if (important === true) {
454
+ return (rule) => {
358
455
  rule.walkDecls((d) => {
359
456
  if (d.parent.type === 'rule' && !inKeyframes(d.parent)) {
360
457
  d.important = true
361
458
  }
362
459
  })
363
- } else if (typeof context.tailwindConfig.important === 'string') {
460
+ }
461
+ }
462
+
463
+ if (typeof important === 'string') {
464
+ return (rule) => {
465
+ rule.selectors = rule.selectors.map((selector) => {
466
+ return `${important} ${selector}`
467
+ })
468
+ }
469
+ }
470
+ })(context.tailwindConfig.important)
471
+
472
+ return allRules.flat(1).map(([{ sort, layer, options }, rule]) => {
473
+ if (options.respectImportant) {
474
+ if (strategy) {
364
475
  let container = postcss.root({ nodes: [rule.clone()] })
365
476
  container.walkRules((r) => {
366
477
  if (inKeyframes(r)) {
367
478
  return
368
479
  }
369
480
 
370
- r.selectors = r.selectors.map((selector) => {
371
- return `${context.tailwindConfig.important} ${selector}`
372
- })
481
+ strategy(r)
373
482
  })
374
483
  rule = container.nodes[0]
375
484
  }
376
485
  }
486
+
377
487
  return [sort | context.layerOrder[layer], rule]
378
488
  })
379
489
  }
@@ -2,60 +2,56 @@ import postcss from 'postcss'
2
2
  import selectorParser from 'postcss-selector-parser'
3
3
  import { flagEnabled } from '../featureFlags'
4
4
 
5
- function isPseudoElement(n) {
6
- if (n.type !== 'pseudo') {
7
- return false
8
- }
9
-
10
- return (
11
- n.value.startsWith('::') ||
12
- [':before', ':after', ':first-line', ':first-letter'].includes(n.value)
13
- )
5
+ let getNode = {
6
+ id(node) {
7
+ return selectorParser.attribute({
8
+ attribute: 'id',
9
+ operator: '=',
10
+ value: node.value,
11
+ quoteMark: '"',
12
+ })
13
+ },
14
14
  }
15
15
 
16
16
  function minimumImpactSelector(nodes) {
17
17
  let rest = nodes
18
- // Keep all pseudo & combinator types (:not([hidden]) ~ :not([hidden]))
19
- .filter((n) => n.type === 'pseudo' || n.type === 'combinator')
20
- // Remove leading pseudo's (:hover, :focus, ...)
21
- .filter((n, idx, all) => {
22
- // Keep pseudo elements
23
- if (isPseudoElement(n)) return true
18
+ .filter((node) => {
19
+ // Keep non-pseudo nodes
20
+ if (node.type !== 'pseudo') return true
21
+
22
+ // Keep pseudo nodes that have subnodes
23
+ // E.g.: `:not()` contains subnodes inside the parentheses
24
+ if (node.nodes.length > 0) return true
25
+
26
+ // Keep pseudo `elements`
27
+ // This implicitly means that we ignore pseudo `classes`
28
+ return (
29
+ node.value.startsWith('::') ||
30
+ [':before', ':after', ':first-line', ':first-letter'].includes(node.value)
31
+ )
32
+ })
33
+ .reverse()
24
34
 
25
- if (idx === 0 && n.type === 'pseudo') return false
26
- if (idx > 0 && n.type === 'pseudo' && all[idx - 1].type === 'pseudo') return false
35
+ let searchFor = new Set(['tag', 'class', 'id', 'attribute'])
27
36
 
28
- return true
29
- })
37
+ let splitPointIdx = rest.findIndex((n) => searchFor.has(n.type))
38
+ if (splitPointIdx === -1) return rest.reverse().join('').trim()
30
39
 
31
- let [bestNode] = nodes
32
-
33
- for (let [type, getNode = (n) => n] of [
34
- ['class'],
35
- [
36
- 'id',
37
- (n) =>
38
- selectorParser.attribute({
39
- attribute: 'id',
40
- operator: '=',
41
- value: n.value,
42
- quoteMark: '"',
43
- }),
44
- ],
45
- ['attribute'],
46
- ]) {
47
- let match = nodes.find((n) => n.type === type)
48
-
49
- if (match) {
50
- bestNode = getNode(match)
51
- break
52
- }
40
+ let node = rest[splitPointIdx]
41
+ let bestNode = getNode[node.type] ? getNode[node.type](node) : node
42
+
43
+ rest = rest.slice(0, splitPointIdx)
44
+
45
+ let combinatorIdx = rest.findIndex((n) => n.type === 'combinator' && n.value === '>')
46
+ if (combinatorIdx !== -1) {
47
+ rest.splice(0, combinatorIdx)
48
+ rest.unshift(selectorParser.universal())
53
49
  }
54
50
 
55
- return [bestNode, ...rest].join('').trim()
51
+ return [bestNode, ...rest.reverse()].join('').trim()
56
52
  }
57
53
 
58
- let elementSelectorParser = selectorParser((selectors) => {
54
+ export let elementSelectorParser = selectorParser((selectors) => {
59
55
  return selectors.map((s) => {
60
56
  let nodes = s.split((n) => n.type === 'combinator' && n.value === ' ').pop()
61
57
  return minimumImpactSelector(nodes)
@@ -17,6 +17,36 @@ import * as sharedState from './sharedState'
17
17
  import { env } from './sharedState'
18
18
  import { toPath } from '../util/toPath'
19
19
  import log from '../util/log'
20
+ import negateValue from '../util/negateValue'
21
+
22
+ function parseVariantFormatString(input) {
23
+ if (input.includes('{')) {
24
+ if (!isBalanced(input)) throw new Error(`Your { and } are unbalanced.`)
25
+
26
+ return input
27
+ .split(/{(.*)}/gim)
28
+ .flatMap((line) => parseVariantFormatString(line))
29
+ .filter(Boolean)
30
+ }
31
+
32
+ return [input.trim()]
33
+ }
34
+
35
+ function isBalanced(input) {
36
+ let count = 0
37
+
38
+ for (let char of input) {
39
+ if (char === '{') {
40
+ count++
41
+ } else if (char === '}') {
42
+ if (--count < 0) {
43
+ return false // unbalanced
44
+ }
45
+ }
46
+ }
47
+
48
+ return count === 0
49
+ }
20
50
 
21
51
  function insertInto(list, value, { before = [] } = {}) {
22
52
  before = [].concat(before)
@@ -176,16 +206,41 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
176
206
  return identifier
177
207
  }
178
208
 
179
- if (typeof context.tailwindConfig.prefix === 'function') {
180
- return prefixSelector(context.tailwindConfig.prefix, `.${identifier}`).substr(1)
181
- }
182
-
183
209
  return context.tailwindConfig.prefix + identifier
184
210
  }
185
211
 
186
212
  return {
187
213
  addVariant(variantName, variantFunctions, options = {}) {
188
- variantFunctions = [].concat(variantFunctions)
214
+ variantFunctions = [].concat(variantFunctions).map((variantFunction) => {
215
+ if (typeof variantFunction !== 'string') {
216
+ // Safelist public API functions
217
+ return ({ modifySelectors, container, separator }) => {
218
+ return variantFunction({ modifySelectors, container, separator })
219
+ }
220
+ }
221
+
222
+ variantFunction = variantFunction
223
+ .replace(/\n+/g, '')
224
+ .replace(/\s{1,}/g, ' ')
225
+ .trim()
226
+
227
+ let fns = parseVariantFormatString(variantFunction)
228
+ .map((str) => {
229
+ if (!str.startsWith('@')) {
230
+ return ({ format }) => format(str)
231
+ }
232
+
233
+ let [, name, params] = /@(.*?) (.*)/g.exec(str)
234
+ return ({ wrap }) => wrap(postcss.atRule({ name, params }))
235
+ })
236
+ .reverse()
237
+
238
+ return (api) => {
239
+ for (let fn of fns) {
240
+ fn(api)
241
+ }
242
+ }
243
+ })
189
244
 
190
245
  insertInto(variantList, variantName, options)
191
246
  variantMap.set(variantName, variantFunctions)
@@ -300,7 +355,7 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
300
355
  function wrapped(modifier, { isOnlyPlugin }) {
301
356
  let { type = 'any' } = options
302
357
  type = [].concat(type)
303
- let [value, coercedType] = coerceValue(type, modifier, options.values, tailwindConfig)
358
+ let [value, coercedType] = coerceValue(type, modifier, options, tailwindConfig)
304
359
 
305
360
  if (value === undefined) {
306
361
  return []
@@ -352,7 +407,7 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
352
407
  function wrapped(modifier, { isOnlyPlugin }) {
353
408
  let { type = 'any' } = options
354
409
  type = [].concat(type)
355
- let [value, coercedType] = coerceValue(type, modifier, options.values, tailwindConfig)
410
+ let [value, coercedType] = coerceValue(type, modifier, options, tailwindConfig)
356
411
 
357
412
  if (value === undefined) {
358
413
  return []
@@ -518,6 +573,7 @@ function resolvePlugins(context, root) {
518
573
  variantPlugins['directionVariants'],
519
574
  variantPlugins['reducedMotionVariants'],
520
575
  variantPlugins['darkVariants'],
576
+ variantPlugins['printVariant'],
521
577
  variantPlugins['screenVariants'],
522
578
  ]
523
579
 
@@ -670,10 +726,16 @@ function registerPlugins(plugins, context) {
670
726
  for (let util of classList) {
671
727
  if (Array.isArray(util)) {
672
728
  let [utilName, options] = util
729
+ let negativeClasses = []
673
730
 
674
- for (let value of Object.keys(options?.values ?? {})) {
675
- output.push(formatClass(utilName, value))
731
+ for (let [key, value] of Object.entries(options?.values ?? {})) {
732
+ output.push(formatClass(utilName, key))
733
+ if (options?.supportsNegativeValues && negateValue(value)) {
734
+ negativeClasses.push(formatClass(utilName, `-${key}`))
735
+ }
676
736
  }
737
+
738
+ output.push(...negativeClasses)
677
739
  } else {
678
740
  output.push(util)
679
741
  }
@@ -84,6 +84,13 @@ function rebootWatcher(context, configPath, configDependencies, candidateFiles)
84
84
 
85
85
  watcher = chokidar.watch([...candidateFiles, ...configDependencies], {
86
86
  ignoreInitial: true,
87
+ awaitWriteFinish:
88
+ process.platform === 'win32'
89
+ ? {
90
+ stabilityThreshold: 50,
91
+ pollInterval: 10,
92
+ }
93
+ : false,
87
94
  })
88
95
 
89
96
  setWatcher(context, watcher)
@@ -1,7 +1,7 @@
1
1
  export const env = {
2
2
  TAILWIND_MODE: process.env.TAILWIND_MODE,
3
3
  NODE_ENV: process.env.NODE_ENV,
4
- DEBUG: process.env.DEBUG !== undefined,
4
+ DEBUG: process.env.DEBUG !== undefined && process.env.DEBUG !== '0',
5
5
  TAILWIND_DISABLE_TOUCH: process.env.TAILWIND_DISABLE_TOUCH !== undefined,
6
6
  TAILWIND_TOUCH_DIR: process.env.TAILWIND_TOUCH_DIR,
7
7
  }
@@ -5,8 +5,10 @@ import evaluateTailwindFunctions from './lib/evaluateTailwindFunctions'
5
5
  import substituteScreenAtRules from './lib/substituteScreenAtRules'
6
6
  import resolveDefaultsAtRules from './lib/resolveDefaultsAtRules'
7
7
  import collapseAdjacentRules from './lib/collapseAdjacentRules'
8
+ import collapseDuplicateDeclarations from './lib/collapseDuplicateDeclarations'
8
9
  import detectNesting from './lib/detectNesting'
9
10
  import { createContext } from './lib/setupContextUtils'
11
+ import { issueFlagNotices } from './featureFlags'
10
12
 
11
13
  export default function processTailwindFeatures(setupContext) {
12
14
  return function (root, result) {
@@ -32,6 +34,8 @@ export default function processTailwindFeatures(setupContext) {
32
34
  )
33
35
  }
34
36
 
37
+ issueFlagNotices(context.tailwindConfig)
38
+
35
39
  detectNesting(context)(root, result)
36
40
  expandTailwindAtRules(context)(root, result)
37
41
  expandApplyAtRules(context)(root, result)
@@ -39,5 +43,6 @@ export default function processTailwindFeatures(setupContext) {
39
43
  substituteScreenAtRules(context)(root, result)
40
44
  resolveDefaultsAtRules(context)(root, result)
41
45
  collapseAdjacentRules(context)(root, result)
46
+ collapseDuplicateDeclarations(context)(root, result)
42
47
  }
43
48
  }
@@ -3,7 +3,7 @@ import transformThemeValue from './transformThemeValue'
3
3
  export default function createUtilityPlugin(
4
4
  themeKey,
5
5
  utilityVariations = [[themeKey, [themeKey]]],
6
- { filterDefault = false, type = 'any' } = {}
6
+ { filterDefault = false, ...options } = {}
7
7
  ) {
8
8
  let transformValue = transformThemeValue(themeKey)
9
9
  return function ({ matchUtilities, theme }) {
@@ -24,12 +24,12 @@ export default function createUtilityPlugin(
24
24
  })
25
25
  }, {}),
26
26
  {
27
+ ...options,
27
28
  values: filterDefault
28
29
  ? Object.fromEntries(
29
30
  Object.entries(theme(themeKey) ?? {}).filter(([modifier]) => modifier !== 'DEFAULT')
30
31
  )
31
32
  : theme(themeKey),
32
- type,
33
33
  }
34
34
  )
35
35
  }