tailwindcss 3.1.8 → 3.2.0

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 (112) hide show
  1. package/README.md +6 -5
  2. package/lib/cli/build/deps.js +54 -0
  3. package/lib/cli/build/index.js +44 -0
  4. package/lib/cli/build/plugin.js +335 -0
  5. package/lib/cli/build/utils.js +78 -0
  6. package/lib/cli/build/watching.js +113 -0
  7. package/lib/cli/help/index.js +71 -0
  8. package/lib/cli/index.js +18 -0
  9. package/lib/cli/init/index.js +46 -0
  10. package/lib/cli/shared.js +12 -0
  11. package/lib/cli.js +11 -590
  12. package/lib/corePlugins.js +332 -108
  13. package/lib/css/preflight.css +5 -0
  14. package/lib/featureFlags.js +7 -4
  15. package/lib/index.js +6 -1
  16. package/lib/lib/content.js +167 -0
  17. package/lib/lib/defaultExtractor.js +15 -10
  18. package/lib/lib/detectNesting.js +2 -2
  19. package/lib/lib/evaluateTailwindFunctions.js +17 -1
  20. package/lib/lib/expandApplyAtRules.js +66 -37
  21. package/lib/lib/expandTailwindAtRules.js +10 -42
  22. package/lib/lib/findAtConfigPath.js +44 -0
  23. package/lib/lib/generateRules.js +180 -93
  24. package/lib/lib/normalizeTailwindDirectives.js +1 -1
  25. package/lib/lib/offsets.js +217 -0
  26. package/lib/lib/regex.js +1 -1
  27. package/lib/lib/setupContextUtils.js +339 -100
  28. package/lib/lib/setupTrackingContext.js +5 -39
  29. package/lib/lib/sharedState.js +2 -0
  30. package/lib/public/colors.js +1 -1
  31. package/lib/util/buildMediaQuery.js +6 -3
  32. package/lib/util/configurePlugins.js +1 -1
  33. package/lib/util/dataTypes.js +15 -19
  34. package/lib/util/formatVariantSelector.js +92 -8
  35. package/lib/util/getAllConfigs.js +14 -3
  36. package/lib/util/isValidArbitraryValue.js +1 -1
  37. package/lib/util/nameClass.js +3 -0
  38. package/lib/util/negateValue.js +15 -2
  39. package/lib/util/normalizeConfig.js +17 -3
  40. package/lib/util/normalizeScreens.js +100 -3
  41. package/lib/util/parseAnimationValue.js +1 -1
  42. package/lib/util/parseBoxShadowValue.js +1 -1
  43. package/lib/util/parseDependency.js +33 -54
  44. package/lib/util/parseGlob.js +34 -0
  45. package/lib/util/parseObjectStyles.js +1 -1
  46. package/lib/util/pluginUtils.js +86 -17
  47. package/lib/util/resolveConfig.js +2 -2
  48. package/lib/util/splitAtTopLevelOnly.js +31 -81
  49. package/lib/util/transformThemeValue.js +9 -2
  50. package/lib/util/validateConfig.js +1 -1
  51. package/lib/util/validateFormalSyntax.js +24 -0
  52. package/package.json +13 -11
  53. package/peers/.DS_Store +0 -0
  54. package/peers/.svgo.yml +75 -0
  55. package/peers/index.js +3332 -2032
  56. package/peers/orders/concentric-css.json +299 -0
  57. package/peers/orders/smacss.json +299 -0
  58. package/peers/orders/source.json +295 -0
  59. package/plugin.d.ts +3 -3
  60. package/scripts/release-channel.js +18 -0
  61. package/scripts/release-notes.js +21 -0
  62. package/src/.DS_Store +0 -0
  63. package/src/cli/build/deps.js +56 -0
  64. package/src/cli/build/index.js +45 -0
  65. package/src/cli/build/plugin.js +397 -0
  66. package/src/cli/build/utils.js +76 -0
  67. package/src/cli/build/watching.js +134 -0
  68. package/src/cli/help/index.js +70 -0
  69. package/src/cli/index.js +3 -0
  70. package/src/cli/init/index.js +50 -0
  71. package/src/cli/shared.js +5 -0
  72. package/src/cli.js +4 -696
  73. package/src/corePlugins.js +262 -39
  74. package/src/css/preflight.css +5 -0
  75. package/src/featureFlags.js +12 -2
  76. package/src/index.js +5 -0
  77. package/src/lib/content.js +205 -0
  78. package/src/lib/defaultExtractor.js +3 -0
  79. package/src/lib/evaluateTailwindFunctions.js +22 -1
  80. package/src/lib/expandApplyAtRules.js +70 -29
  81. package/src/lib/expandTailwindAtRules.js +8 -46
  82. package/src/lib/findAtConfigPath.js +48 -0
  83. package/src/lib/generateRules.js +223 -101
  84. package/src/lib/offsets.js +270 -0
  85. package/src/lib/setupContextUtils.js +376 -89
  86. package/src/lib/setupTrackingContext.js +4 -45
  87. package/src/lib/sharedState.js +2 -0
  88. package/src/util/buildMediaQuery.js +5 -3
  89. package/src/util/dataTypes.js +15 -17
  90. package/src/util/formatVariantSelector.js +113 -9
  91. package/src/util/getAllConfigs.js +14 -2
  92. package/src/util/nameClass.js +4 -0
  93. package/src/util/negateValue.js +10 -2
  94. package/src/util/normalizeConfig.js +22 -2
  95. package/src/util/normalizeScreens.js +99 -4
  96. package/src/util/parseBoxShadowValue.js +1 -1
  97. package/src/util/parseDependency.js +37 -42
  98. package/src/util/parseGlob.js +24 -0
  99. package/src/util/pluginUtils.js +90 -14
  100. package/src/util/resolveConfig.js +1 -1
  101. package/src/util/splitAtTopLevelOnly.js +23 -49
  102. package/src/util/transformThemeValue.js +9 -1
  103. package/src/util/validateFormalSyntax.js +34 -0
  104. package/stubs/defaultConfig.stub.js +19 -3
  105. package/tmp.css +11 -0
  106. package/tmp.dependency-graph.js +2 -0
  107. package/tmp.in.css +3 -0
  108. package/tmp.js +0 -0
  109. package/tmp.out.css +524 -0
  110. package/types/config.d.ts +47 -13
  111. package/types/generated/default-theme.d.ts +11 -0
  112. package/CHANGELOG.md +0 -2231
@@ -1,22 +1,14 @@
1
1
  import fs from 'fs'
2
- import path from 'path'
3
-
4
- import fastGlob from 'fast-glob'
5
2
  import LRU from 'quick-lru'
6
- import normalizePath from 'normalize-path'
7
3
 
8
4
  import hash from '../util/hashConfig'
9
5
  import getModuleDependencies from '../lib/getModuleDependencies'
10
-
11
6
  import resolveConfig from '../public/resolve-config'
12
-
13
7
  import resolveConfigPath from '../util/resolveConfigPath'
14
-
15
- import { env } from './sharedState'
16
-
17
8
  import { getContext, getFileModifiedMap } from './setupContextUtils'
18
9
  import parseDependency from '../util/parseDependency'
19
10
  import { validateConfig } from '../util/validateConfig.js'
11
+ import { parseCandidateFiles, resolvedChangedContent } from './content.js'
20
12
 
21
13
  let configPathCache = new LRU({ maxSize: 100 })
22
14
 
@@ -27,9 +19,7 @@ function getCandidateFiles(context, tailwindConfig) {
27
19
  return candidateFilesCache.get(context)
28
20
  }
29
21
 
30
- let candidateFiles = tailwindConfig.content.files
31
- .filter((item) => typeof item === 'string')
32
- .map((contentPath) => normalizePath(contentPath))
22
+ let candidateFiles = parseCandidateFiles(context, tailwindConfig)
33
23
 
34
24
  return candidateFilesCache.set(context, candidateFiles).get(context)
35
25
  }
@@ -80,36 +70,6 @@ function getTailwindConfig(configOrPath) {
80
70
  return [newConfig, null, hash(newConfig), []]
81
71
  }
82
72
 
83
- function resolvedChangedContent(context, candidateFiles, fileModifiedMap) {
84
- let changedContent = context.tailwindConfig.content.files
85
- .filter((item) => typeof item.raw === 'string')
86
- .map(({ raw, extension = 'html' }) => ({ content: raw, extension }))
87
-
88
- for (let changedFile of resolveChangedFiles(candidateFiles, fileModifiedMap)) {
89
- let content = fs.readFileSync(changedFile, 'utf8')
90
- let extension = path.extname(changedFile).slice(1)
91
- changedContent.push({ content, extension })
92
- }
93
- return changedContent
94
- }
95
-
96
- function resolveChangedFiles(candidateFiles, fileModifiedMap) {
97
- let changedFiles = new Set()
98
- env.DEBUG && console.time('Finding changed files')
99
- let files = fastGlob.sync(candidateFiles)
100
- for (let file of files) {
101
- let prevModified = fileModifiedMap.has(file) ? fileModifiedMap.get(file) : -Infinity
102
- let modified = fs.statSync(file).mtimeMs
103
-
104
- if (modified > prevModified) {
105
- changedFiles.add(file)
106
- fileModifiedMap.set(file, modified)
107
- }
108
- }
109
- env.DEBUG && console.timeEnd('Finding changed files')
110
- return changedFiles
111
- }
112
-
113
73
  // DISABLE_TOUCH = TRUE
114
74
 
115
75
  // Retrieve an existing context from cache if possible (since contexts are unique per
@@ -161,9 +121,8 @@ export default function setupTrackingContext(configOrPath) {
161
121
  let fileModifiedMap = getFileModifiedMap(context)
162
122
 
163
123
  // Add template paths as postcss dependencies.
164
- for (let fileOrGlob of candidateFiles) {
165
- let dependency = parseDependency(fileOrGlob)
166
- if (dependency) {
124
+ for (let contentPath of candidateFiles) {
125
+ for (let dependency of parseDependency(contentPath)) {
167
126
  registerDependency(dependency)
168
127
  }
169
128
  }
@@ -8,6 +8,8 @@ export const contextSourcesMap = new Map()
8
8
  export const sourceHashMap = new Map()
9
9
  export const NOT_ON_DEMAND = new String('*')
10
10
 
11
+ export const NONE = Symbol('__NONE__')
12
+
11
13
  export function resolveDebug(debug) {
12
14
  if (debug === undefined) {
13
15
  return false
@@ -2,8 +2,8 @@ export default function buildMediaQuery(screens) {
2
2
  screens = Array.isArray(screens) ? screens : [screens]
3
3
 
4
4
  return screens
5
- .map((screen) =>
6
- screen.values.map((screen) => {
5
+ .map((screen) => {
6
+ let values = screen.values.map((screen) => {
7
7
  if (screen.raw !== undefined) {
8
8
  return screen.raw
9
9
  }
@@ -15,6 +15,8 @@ export default function buildMediaQuery(screens) {
15
15
  .filter(Boolean)
16
16
  .join(' and ')
17
17
  })
18
- )
18
+
19
+ return screen.not ? `not all and ${values}` : values
20
+ })
19
21
  .join(', ')
20
22
  }
@@ -1,12 +1,14 @@
1
1
  import { parseColor } from './color'
2
2
  import { parseBoxShadowValue } from './parseBoxShadowValue'
3
+ import { splitAtTopLevelOnly } from './splitAtTopLevelOnly'
3
4
 
4
5
  let cssFunctions = ['min', 'max', 'clamp', 'calc']
5
6
 
6
7
  // Ref: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Types
7
8
 
8
- let COMMA = /,(?![^(]*\))/g // Comma separator that is not located between brackets. E.g.: `cubiz-bezier(a, b, c)` these don't count.
9
- let UNDERSCORE = /_(?![^(]*\))/g // Underscore separator that is not located between brackets. E.g.: `rgba(255,_255,_255)_black` these don't count.
9
+ function isCSSFunction(value) {
10
+ return cssFunctions.some((fn) => new RegExp(`^${fn}\\(.*\\)`).test(value))
11
+ }
10
12
 
11
13
  // This is not a data type, but rather a function that can normalize the
12
14
  // correct values.
@@ -57,13 +59,11 @@ export function url(value) {
57
59
  }
58
60
 
59
61
  export function number(value) {
60
- return !isNaN(Number(value)) || cssFunctions.some((fn) => new RegExp(`^${fn}\\(.+?`).test(value))
62
+ return !isNaN(Number(value)) || isCSSFunction(value)
61
63
  }
62
64
 
63
65
  export function percentage(value) {
64
- return value.split(UNDERSCORE).every((part) => {
65
- return /%$/g.test(part) || cssFunctions.some((fn) => new RegExp(`^${fn}\\(.+?%`).test(part))
66
- })
66
+ return (value.endsWith('%') && number(value.slice(0, -1))) || isCSSFunction(value)
67
67
  }
68
68
 
69
69
  let lengthUnits = [
@@ -86,13 +86,11 @@ let lengthUnits = [
86
86
  ]
87
87
  let lengthUnitsPattern = `(?:${lengthUnits.join('|')})`
88
88
  export function length(value) {
89
- return value.split(UNDERSCORE).every((part) => {
90
- return (
91
- part === '0' ||
92
- new RegExp(`${lengthUnitsPattern}$`).test(part) ||
93
- cssFunctions.some((fn) => new RegExp(`^${fn}\\(.+?${lengthUnitsPattern}`).test(part))
94
- )
95
- })
89
+ return (
90
+ value === '0' ||
91
+ new RegExp(`^[+-]?[0-9]*\.?[0-9]+(?:[eE][+-]?[0-9]+)?${lengthUnitsPattern}$`).test(value) ||
92
+ isCSSFunction(value)
93
+ )
96
94
  }
97
95
 
98
96
  let lineWidths = new Set(['thin', 'medium', 'thick'])
@@ -115,7 +113,7 @@ export function shadow(value) {
115
113
  export function color(value) {
116
114
  let colors = 0
117
115
 
118
- let result = value.split(UNDERSCORE).every((part) => {
116
+ let result = splitAtTopLevelOnly(value, '_').every((part) => {
119
117
  part = normalize(part)
120
118
 
121
119
  if (part.startsWith('var(')) return true
@@ -130,7 +128,7 @@ export function color(value) {
130
128
 
131
129
  export function image(value) {
132
130
  let images = 0
133
- let result = value.split(COMMA).every((part) => {
131
+ let result = splitAtTopLevelOnly(value, ',').every((part) => {
134
132
  part = normalize(part)
135
133
 
136
134
  if (part.startsWith('var(')) return true
@@ -171,7 +169,7 @@ export function gradient(value) {
171
169
  let validPositions = new Set(['center', 'top', 'right', 'bottom', 'left'])
172
170
  export function position(value) {
173
171
  let positions = 0
174
- let result = value.split(UNDERSCORE).every((part) => {
172
+ let result = splitAtTopLevelOnly(value, '_').every((part) => {
175
173
  part = normalize(part)
176
174
 
177
175
  if (part.startsWith('var(')) return true
@@ -189,7 +187,7 @@ export function position(value) {
189
187
 
190
188
  export function familyName(value) {
191
189
  let fonts = 0
192
- let result = value.split(COMMA).every((part) => {
190
+ let result = splitAtTopLevelOnly(value, ',').every((part) => {
193
191
  part = normalize(part)
194
192
 
195
193
  if (part.startsWith('var(')) return true
@@ -29,6 +29,81 @@ export function formatVariantSelector(current, ...others) {
29
29
  return current
30
30
  }
31
31
 
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
+ }
49
+
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 !== ':merge') {
73
+ return -1
74
+ } else if (a.type === 'pseudo' && a.value !== ':merge' && 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
86
+
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
+
32
107
  export function finalizeSelector(
33
108
  format,
34
109
  {
@@ -63,13 +138,7 @@ export function finalizeSelector(
63
138
  // Remove extraneous selectors that do not include the base class/candidate being matched against
64
139
  // For example if we have a utility defined `.a, .b { color: red}`
65
140
  // And the formatted variant is sm:b then we want the final selector to be `.sm\:b` and not `.a, .sm\:b`
66
- ast.each((node) => {
67
- let hasClassesMatchingCandidate = node.some((n) => n.type === 'class' && n.value === base)
68
-
69
- if (!hasClassesMatchingCandidate) {
70
- node.remove()
71
- }
72
- })
141
+ ast.each((sel) => eliminateIrrelevantSelectors(sel, base))
73
142
 
74
143
  // Normalize escaped classes, e.g.:
75
144
  //
@@ -88,12 +157,47 @@ export function finalizeSelector(
88
157
  }
89
158
  })
90
159
 
160
+ let simpleStart = selectorParser.comment({ value: '/*__simple__*/' })
161
+ let simpleEnd = selectorParser.comment({ value: '/*__simple__*/' })
162
+
91
163
  // We can safely replace the escaped base now, since the `base` section is
92
164
  // now in a normalized escaped value.
93
165
  ast.walkClasses((node) => {
94
- if (node.value === base) {
95
- node.replaceWith(...formatAst.nodes)
166
+ if (node.value !== base) {
167
+ return
96
168
  }
169
+
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()
97
201
  })
98
202
 
99
203
  // This will make sure to move pseudo's to the correct spot (the end for
@@ -11,9 +11,21 @@ export default function getAllConfigs(config) {
11
11
  // Add experimental configs here...
12
12
  respectDefaultRingColorOpacity: {
13
13
  theme: {
14
- ringColor: {
14
+ ringColor: ({ theme }) => ({
15
15
  DEFAULT: '#3b82f67f',
16
- },
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,
17
29
  },
18
30
  },
19
31
  }
@@ -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
@@ -154,6 +164,16 @@ export function normalizeConfig(config) {
154
164
 
155
165
  // Normalize the `content`
156
166
  config.content = {
167
+ relative: (() => {
168
+ let { content } = config
169
+
170
+ if (content?.relative) {
171
+ return content.relative
172
+ }
173
+
174
+ return config.future?.relativeContentPathsByDefault ?? false
175
+ })(),
176
+
157
177
  files: (() => {
158
178
  let { content, purge } = config
159
179
 
@@ -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
  }
@@ -5,7 +5,7 @@ let SPACE = /\ +(?![^(]*\))/g // Similar to the one above, but with spaces inste
5
5
  let LENGTH = /^-?(\d+|\.\d+)(.*?)$/g
6
6
 
7
7
  export function parseBoxShadowValue(input) {
8
- let shadows = Array.from(splitAtTopLevelOnly(input, ','))
8
+ let shadows = splitAtTopLevelOnly(input, ',')
9
9
  return shadows.map((shadow) => {
10
10
  let value = shadow.trim()
11
11
  let result = { raw: value }
@@ -1,49 +1,44 @@
1
- import isGlob from 'is-glob'
2
- import globParent from 'glob-parent'
3
- import path from 'path'
4
-
5
- // Based on `glob-base`
6
- // https://github.com/micromatch/glob-base/blob/master/index.js
7
- function parseGlob(pattern) {
8
- let glob = pattern
9
- let base = globParent(pattern)
10
-
11
- if (base !== '.') {
12
- glob = pattern.substr(base.length)
13
- if (glob.charAt(0) === '/') {
14
- glob = glob.substr(1)
15
- }
16
- }
17
-
18
- if (glob.substr(0, 2) === './') {
19
- glob = glob.substr(2)
20
- }
21
- if (glob.charAt(0) === '/') {
22
- glob = glob.substr(1)
1
+ // @ts-check
2
+
3
+ /**
4
+ * @typedef {{type: 'dependency', file: string} | {type: 'dir-dependency', dir: string, glob: string}} Dependency
5
+ */
6
+
7
+ /**
8
+ *
9
+ * @param {import('../lib/content.js').ContentPath} contentPath
10
+ * @returns {Dependency[]}
11
+ */
12
+ export default function parseDependency(contentPath) {
13
+ if (contentPath.ignore) {
14
+ return []
23
15
  }
24
16
 
25
- return { base, glob }
26
- }
27
-
28
- export default function parseDependency(normalizedFileOrGlob) {
29
- if (normalizedFileOrGlob.startsWith('!')) {
30
- return null
31
- }
32
-
33
- let message
34
-
35
- if (isGlob(normalizedFileOrGlob)) {
36
- let { base, glob } = parseGlob(normalizedFileOrGlob)
37
- message = { type: 'dir-dependency', dir: path.resolve(base), glob }
38
- } else {
39
- message = { type: 'dependency', file: path.resolve(normalizedFileOrGlob) }
17
+ if (!contentPath.glob) {
18
+ return [
19
+ {
20
+ type: 'dependency',
21
+ file: contentPath.base,
22
+ },
23
+ ]
40
24
  }
41
25
 
42
- // rollup-plugin-postcss does not support dir-dependency messages
43
- // but directories can be watched in the same way as files
44
- if (message.type === 'dir-dependency' && process.env.ROLLUP_WATCH === 'true') {
45
- message = { type: 'dependency', file: message.dir }
26
+ if (process.env.ROLLUP_WATCH === 'true') {
27
+ // rollup-plugin-postcss does not support dir-dependency messages
28
+ // but directories can be watched in the same way as files
29
+ return [
30
+ {
31
+ type: 'dependency',
32
+ file: contentPath.base,
33
+ },
34
+ ]
46
35
  }
47
36
 
48
- return message
37
+ return [
38
+ {
39
+ type: 'dir-dependency',
40
+ dir: contentPath.base,
41
+ glob: contentPath.glob,
42
+ },
43
+ ]
49
44
  }