tailwindcss 3.2.4 → 3.2.6

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 (98) hide show
  1. package/CHANGELOG.md +51 -1
  2. package/README.md +1 -1
  3. package/lib/cli/build/index.js +5 -1
  4. package/lib/cli/build/plugin.js +39 -34
  5. package/lib/cli/index.js +231 -10
  6. package/lib/cli/init/index.js +2 -2
  7. package/lib/cli.js +4 -226
  8. package/lib/corePlugins.js +45 -27
  9. package/lib/featureFlags.js +8 -8
  10. package/lib/index.js +4 -46
  11. package/lib/lib/collapseAdjacentRules.js +2 -2
  12. package/lib/lib/collapseDuplicateDeclarations.js +2 -2
  13. package/lib/lib/content.js +16 -16
  14. package/lib/lib/defaultExtractor.js +10 -5
  15. package/lib/lib/detectNesting.js +7 -1
  16. package/lib/lib/evaluateTailwindFunctions.js +4 -4
  17. package/lib/lib/expandApplyAtRules.js +2 -2
  18. package/lib/lib/expandTailwindAtRules.js +34 -9
  19. package/lib/lib/findAtConfigPath.js +3 -3
  20. package/lib/lib/generateRules.js +105 -50
  21. package/lib/lib/offsets.js +88 -1
  22. package/lib/lib/remap-bitfield.js +87 -0
  23. package/lib/lib/resolveDefaultsAtRules.js +4 -4
  24. package/lib/lib/setupContextUtils.js +121 -80
  25. package/lib/lib/setupTrackingContext.js +25 -4
  26. package/lib/lib/sharedState.js +19 -1
  27. package/lib/oxide/cli/build/deps.js +81 -0
  28. package/lib/oxide/cli/build/index.js +47 -0
  29. package/lib/oxide/cli/build/plugin.js +364 -0
  30. package/lib/oxide/cli/build/utils.js +77 -0
  31. package/lib/oxide/cli/build/watching.js +177 -0
  32. package/lib/oxide/cli/help/index.js +70 -0
  33. package/lib/oxide/cli/index.js +220 -0
  34. package/lib/oxide/cli/init/index.js +35 -0
  35. package/lib/oxide/cli.js +5 -0
  36. package/lib/oxide/postcss-plugin.js +2 -0
  37. package/lib/plugin.js +98 -0
  38. package/lib/postcss-plugins/nesting/plugin.js +2 -2
  39. package/lib/util/cloneNodes.js +2 -2
  40. package/lib/util/color.js +20 -6
  41. package/lib/util/createUtilityPlugin.js +2 -2
  42. package/lib/util/dataTypes.js +26 -2
  43. package/lib/util/defaults.js +4 -4
  44. package/lib/util/escapeClassName.js +3 -3
  45. package/lib/util/formatVariantSelector.js +171 -105
  46. package/lib/util/getAllConfigs.js +2 -2
  47. package/lib/util/{isValidArbitraryValue.js → isSyntacticallyValidPropertyValue.js} +2 -2
  48. package/lib/util/negateValue.js +2 -2
  49. package/lib/util/normalizeConfig.js +22 -22
  50. package/lib/util/pluginUtils.js +38 -40
  51. package/lib/util/prefixSelector.js +22 -8
  52. package/lib/util/resolveConfig.js +8 -10
  53. package/package.json +30 -19
  54. package/peers/index.js +61 -42
  55. package/resolveConfig.d.ts +11 -2
  56. package/scripts/swap-engines.js +40 -0
  57. package/src/cli/build/index.js +6 -2
  58. package/src/cli/build/plugin.js +14 -9
  59. package/src/cli/index.js +234 -3
  60. package/src/cli.js +4 -220
  61. package/src/corePlugins.js +31 -3
  62. package/src/index.js +4 -46
  63. package/src/lib/content.js +12 -17
  64. package/src/lib/defaultExtractor.js +9 -3
  65. package/src/lib/detectNesting.js +9 -1
  66. package/src/lib/expandTailwindAtRules.js +35 -6
  67. package/src/lib/generateRules.js +90 -28
  68. package/src/lib/offsets.js +104 -1
  69. package/src/lib/remap-bitfield.js +82 -0
  70. package/src/lib/setupContextUtils.js +97 -55
  71. package/src/lib/setupTrackingContext.js +31 -6
  72. package/src/lib/sharedState.js +17 -0
  73. package/src/oxide/cli/build/deps.ts +91 -0
  74. package/src/oxide/cli/build/index.ts +47 -0
  75. package/src/oxide/cli/build/plugin.ts +436 -0
  76. package/src/oxide/cli/build/utils.ts +74 -0
  77. package/src/oxide/cli/build/watching.ts +225 -0
  78. package/src/oxide/cli/help/index.ts +69 -0
  79. package/src/oxide/cli/index.ts +212 -0
  80. package/src/oxide/cli/init/index.ts +32 -0
  81. package/src/oxide/cli.ts +1 -0
  82. package/src/oxide/postcss-plugin.ts +1 -0
  83. package/src/plugin.js +107 -0
  84. package/src/util/color.js +17 -2
  85. package/src/util/dataTypes.js +29 -4
  86. package/src/util/formatVariantSelector.js +215 -122
  87. package/src/util/{isValidArbitraryValue.js → isSyntacticallyValidPropertyValue.js} +1 -1
  88. package/src/util/negateValue.js +1 -1
  89. package/src/util/pluginUtils.js +22 -19
  90. package/src/util/prefixSelector.js +28 -10
  91. package/src/util/resolveConfig.js +0 -2
  92. package/stubs/defaultConfig.stub.js +149 -165
  93. package/types/config.d.ts +7 -2
  94. package/types/generated/default-theme.d.ts +77 -77
  95. package/lib/cli/shared.js +0 -12
  96. package/scripts/install-integrations.js +0 -27
  97. package/scripts/rebuildFixtures.js +0 -68
  98. package/src/cli/shared.js +0 -5
@@ -10,6 +10,9 @@ function isCSSFunction(value) {
10
10
  return cssFunctions.some((fn) => new RegExp(`^${fn}\\(.*\\)`).test(value))
11
11
  }
12
12
 
13
+ const placeholder = '--tw-placeholder'
14
+ const placeholderRe = new RegExp(placeholder, 'g')
15
+
13
16
  // This is not a data type, but rather a function that can normalize the
14
17
  // correct values.
15
18
  export function normalize(value, isRoot = true) {
@@ -45,10 +48,14 @@ export function normalize(value, isRoot = true) {
45
48
  // Add spaces around operators inside math functions like calc() that do not follow an operator
46
49
  // or '('.
47
50
  value = value.replace(/(calc|min|max|clamp)\(.+\)/g, (match) => {
48
- return match.replace(
49
- /(-?\d*\.?\d(?!\b-.+[,)](?![^+\-/*])\D)(?:%|[a-z]+)?|\))([+\-/*])/g,
50
- '$1 $2 '
51
- )
51
+ let vars = []
52
+ return match
53
+ .replace(/var\((--.+?)[,)]/g, (match, g1) => {
54
+ vars.push(g1)
55
+ return match.replace(g1, placeholder)
56
+ })
57
+ .replace(/(-?\d*\.?\d(?!\b-\d.+[,)](?![^+\-/*])\D)(?:%|[a-z]+)?|\))([+\-/*])/g, '$1 $2 ')
58
+ .replace(placeholderRe, () => vars.shift())
52
59
  })
53
60
 
54
61
  return value
@@ -66,6 +73,9 @@ export function percentage(value) {
66
73
  return (value.endsWith('%') && number(value.slice(0, -1))) || isCSSFunction(value)
67
74
  }
68
75
 
76
+ // Please refer to MDN when updating this list:
77
+ // https://developer.mozilla.org/en-US/docs/Learn/CSS/Building_blocks/Values_and_units
78
+ // https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Container_Queries#container_query_length_units
69
79
  let lengthUnits = [
70
80
  'cm',
71
81
  'mm',
@@ -79,10 +89,25 @@ let lengthUnits = [
79
89
  'ch',
80
90
  'rem',
81
91
  'lh',
92
+ 'rlh',
82
93
  'vw',
83
94
  'vh',
84
95
  'vmin',
85
96
  'vmax',
97
+ 'vb',
98
+ 'vi',
99
+ 'svw',
100
+ 'svh',
101
+ 'lvw',
102
+ 'lvh',
103
+ 'dvw',
104
+ 'dvh',
105
+ 'cqw',
106
+ 'cqh',
107
+ 'cqi',
108
+ 'cqb',
109
+ 'cqmin',
110
+ 'cqmax',
86
111
  ]
87
112
  let lengthUnitsPattern = `(?:${lengthUnits.join('|')})`
88
113
  export function length(value) {
@@ -3,30 +3,57 @@ 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
-
9
- export let selectorFunctions = new Set([MERGE])
10
-
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]
20
-
21
- current = current.replace(existingTarget, existingTarget + addition)
22
- continue
23
- }
16
+
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),
24
32
  }
33
+ })
25
34
 
26
- current = other.replace(PARENT, current)
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
27
54
  }
28
55
 
29
- return current
56
+ return formatAst
30
57
  }
31
58
 
32
59
  /**
@@ -35,11 +62,11 @@ export function formatVariantSelector(current, ...others) {
35
62
  * Technically :is(), :not(), :has(), etc… can have combinators but those are nested
36
63
  * inside the relevant node and won't be picked up so they're fine to ignore
37
64
  *
38
- * @param {import('postcss-selector-parser').Node} node
39
- * @returns {import('postcss-selector-parser').Node[]}
65
+ * @param {Node} node
66
+ * @returns {Node[]}
40
67
  **/
41
68
  function simpleSelectorForNode(node) {
42
- /** @type {import('postcss-selector-parser').Node[]} */
69
+ /** @type {Node[]} */
43
70
  let nodes = []
44
71
 
45
72
  // Walk backwards until we hit a combinator node (or the start)
@@ -60,8 +87,8 @@ function simpleSelectorForNode(node) {
60
87
  * Resorts the nodes in a selector to ensure they're in the correct order
61
88
  * Tags go before classes, and pseudo classes go after classes
62
89
  *
63
- * @param {import('postcss-selector-parser').Selector} sel
64
- * @returns {import('postcss-selector-parser').Selector}
90
+ * @param {Selector} sel
91
+ * @returns {Selector}
65
92
  **/
66
93
  function resortSelector(sel) {
67
94
  sel.sort((a, b) => {
@@ -81,6 +108,18 @@ function resortSelector(sel) {
81
108
  return sel
82
109
  }
83
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
+ */
84
123
  function eliminateIrrelevantSelectors(sel, base) {
85
124
  let hasClassesMatchingCandidate = false
86
125
 
@@ -104,41 +143,26 @@ function eliminateIrrelevantSelectors(sel, base) {
104
143
  // TODO: Can we do this for :matches, :is, and :where?
105
144
  }
106
145
 
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) {
131
- format = prefixSelector(context.tailwindConfig.prefix, format)
132
- }
133
-
134
- format = format.replace(PARENT, `.${escapeClassName(candidate)}`)
135
-
136
- let formatAst = selectorParser().astSync(format)
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 }) {
153
+ let separator = context?.tailwindConfig?.separator ?? ':'
154
+
155
+ // Split by the separator, but ignore the separator inside square brackets:
156
+ //
157
+ // E.g.: dark:lg:hover:[paint-order:markers]
158
+ //
159
+ // │ │ │ ╰── We will not split here
160
+ // ╰──┴─────┴─────────────── We will split here
161
+ //
162
+ base = base ?? candidate.split(new RegExp(`\\${separator}(?![^[]*\\])`)).pop()
137
163
 
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))
164
+ // Parse the selector into an AST
165
+ let selector = selectorParser().astSync(current)
142
166
 
143
167
  // Normalize escaped classes, e.g.:
144
168
  //
@@ -151,18 +175,31 @@ export function finalizeSelector(
151
175
  // base in selector: bg-\\[rgb\\(255\\,0\\,0\\)\\]
152
176
  // escaped base: bg-\\[rgb\\(255\\2c 0\\2c 0\\)\\]
153
177
  //
154
- ast.walkClasses((node) => {
178
+ selector.walkClasses((node) => {
155
179
  if (node.raws && node.value.includes(base)) {
156
180
  node.raws.value = escapeClassName(unescape(node.raws.value))
157
181
  }
158
182
  })
159
183
 
184
+ // Remove extraneous selectors that do not include the base candidate
185
+ selector.each((sel) => eliminateIrrelevantSelectors(sel, base))
186
+
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
+
160
197
  let simpleStart = selectorParser.comment({ value: '/*__simple__*/' })
161
198
  let simpleEnd = selectorParser.comment({ value: '/*__simple__*/' })
162
199
 
163
200
  // We can safely replace the escaped base now, since the `base` section is
164
201
  // now in a normalized escaped value.
165
- ast.walkClasses((node) => {
202
+ selector.walkClasses((node) => {
166
203
  if (node.value !== base) {
167
204
  return
168
205
  }
@@ -181,7 +218,7 @@ export function finalizeSelector(
181
218
  parent.insertAfter(simpleSelector[simpleSelector.length - 1], simpleEnd)
182
219
 
183
220
  for (let child of formatNodes) {
184
- parent.insertBefore(simpleSelector[0], child)
221
+ parent.insertBefore(simpleSelector[0], child.clone())
185
222
  }
186
223
 
187
224
  node.remove()
@@ -200,47 +237,86 @@ export function finalizeSelector(
200
237
  simpleEnd.remove()
201
238
  })
202
239
 
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)
219
- }
220
-
221
- if (node?.nodes) {
222
- nodes.push(...collectPseudoElements(node))
223
- }
240
+ // Remove unnecessary pseudo selectors that we used as placeholders
241
+ selector.walkPseudos((p) => {
242
+ if (p.value === MERGE) {
243
+ p.replaceWith(p.nodes)
224
244
  }
245
+ })
225
246
 
226
- return nodes
227
- }
228
-
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
- }
235
- })
236
-
237
- let pseudoElements = collectPseudoElements(selector)
247
+ // Move pseudo elements to the end of the selector (if necessary)
248
+ selector.each((sel) => {
249
+ let pseudoElements = collectPseudoElements(sel)
238
250
  if (pseudoElements.length > 0) {
239
- selector.nodes.push(pseudoElements.sort(sortSelector))
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(),
273
+ })
240
274
  }
241
275
  })
242
276
 
243
- return ast.toString()
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
+ )
307
+
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]
244
320
  }
245
321
 
246
322
  // Note: As a rule, double colons (::) should be used instead of a single colon
@@ -250,7 +326,49 @@ export function finalizeSelector(
250
326
  let pseudoElementsBC = [':before', ':after', ':first-line', ':first-letter']
251
327
 
252
328
  // These pseudo-elements _can_ be combined with other pseudo selectors AND the order does matter.
253
- 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
+ }
254
372
 
255
373
  // This will make sure to move pseudo's to the correct spot (the end for
256
374
  // pseudo elements) because otherwise the selector will never work
@@ -292,28 +410,3 @@ function isPseudoElement(node) {
292
410
 
293
411
  return node.value.startsWith('::') || pseudoElementsBC.includes(node.value)
294
412
  }
295
-
296
- function resolveFunctionArgument(haystack, needle, arg) {
297
- let startIdx = haystack.indexOf(arg ? `${needle}(${arg})` : needle)
298
- if (startIdx === -1) return null
299
-
300
- // Start inside the `(`
301
- startIdx += needle.length + 1
302
-
303
- let target = ''
304
- let count = 0
305
-
306
- for (let char of haystack.slice(startIdx)) {
307
- if (char !== '(' && char !== ')') {
308
- target += char
309
- } else if (char === '(') {
310
- target += char
311
- count++
312
- } else if (char === ')') {
313
- if (--count < 0) break // unbalanced
314
- target += char
315
- }
316
- }
317
-
318
- return target
319
- }
@@ -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
 
@@ -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') {
@@ -133,18 +133,14 @@ export function parseColorFormat(value) {
133
133
  return value
134
134
  }
135
135
 
136
- export function asColor(
137
- _,
138
- options = {},
139
- { tailwindConfig = {}, utilityModifier, rawModifier } = {}
140
- ) {
141
- if (options.values?.[rawModifier] !== undefined) {
142
- return parseColorFormat(options.values?.[rawModifier])
136
+ export function asColor(modifier, options = {}, { tailwindConfig = {} } = {}) {
137
+ if (options.values?.[modifier] !== undefined) {
138
+ return parseColorFormat(options.values?.[modifier])
143
139
  }
144
140
 
145
141
  // TODO: Hoist this up to getMatchingTypes or something
146
142
  // We do this here because we need the alpha value (if any)
147
- let [color, alpha] = splitUtilityModifier(rawModifier)
143
+ let [color, alpha] = splitUtilityModifier(modifier)
148
144
 
149
145
  if (alpha !== undefined) {
150
146
  let normalizedColor =
@@ -167,7 +163,7 @@ export function asColor(
167
163
  return withAlphaValue(normalizedColor, tailwindConfig.theme.opacity[alpha])
168
164
  }
169
165
 
170
- return asValue(rawModifier, options, { rawModifier, utilityModifier, validate: validateColor })
166
+ return asValue(modifier, options, { validate: validateColor })
171
167
  }
172
168
 
173
169
  export function asLookupValue(modifier, options = {}) {
@@ -175,8 +171,8 @@ export function asLookupValue(modifier, options = {}) {
175
171
  }
176
172
 
177
173
  function guess(validate) {
178
- return (modifier, options, extras) => {
179
- return asValue(modifier, options, { ...extras, validate })
174
+ return (modifier, options) => {
175
+ return asValue(modifier, options, { validate })
180
176
  }
181
177
  }
182
178
 
@@ -208,6 +204,20 @@ function splitAtFirst(input, delim) {
208
204
  }
209
205
 
210
206
  export function coerceValue(types, modifier, options, tailwindConfig) {
207
+ if (options.values && modifier in options.values) {
208
+ for (let { type } of types ?? []) {
209
+ let result = typeMap[type](modifier, options, {
210
+ tailwindConfig,
211
+ })
212
+
213
+ if (result === undefined) {
214
+ continue
215
+ }
216
+
217
+ return [result, type, null]
218
+ }
219
+ }
220
+
211
221
  if (isArbitraryValue(modifier)) {
212
222
  let arbitraryValue = modifier.slice(1, -1)
213
223
  let [explicitType, value] = splitAtFirst(arbitraryValue, ':')
@@ -280,17 +290,10 @@ export function* getMatchingTypes(types, rawModifier, options, tailwindConfig) {
280
290
  utilityModifier = utilityModifier.slice(1, -1)
281
291
  }
282
292
  }
283
-
284
- let result = asValue(rawModifier, options, { rawModifier, utilityModifier, tailwindConfig })
285
- if (result !== undefined) {
286
- yield [result, 'any', null]
287
- }
288
293
  }
289
294
 
290
- for (const { type } of types ?? []) {
295
+ for (let { type } of types ?? []) {
291
296
  let result = typeMap[type](modifier, options, {
292
- rawModifier,
293
- utilityModifier,
294
297
  tailwindConfig,
295
298
  })
296
299
 
@@ -1,14 +1,32 @@
1
1
  import parser from 'postcss-selector-parser'
2
2
 
3
+ /**
4
+ * @template {string | import('postcss-selector-parser').Root} T
5
+ *
6
+ * Prefix all classes in the selector with the given prefix
7
+ *
8
+ * It can take either a string or a selector AST and will return the same type
9
+ *
10
+ * @param {string} prefix
11
+ * @param {T} selector
12
+ * @param {boolean} prependNegative
13
+ * @returns {T}
14
+ */
3
15
  export default function (prefix, selector, prependNegative = false) {
4
- return parser((selectors) => {
5
- selectors.walkClasses((classSelector) => {
6
- let baseClass = classSelector.value
7
- let shouldPlaceNegativeBeforePrefix = prependNegative && baseClass.startsWith('-')
8
-
9
- classSelector.value = shouldPlaceNegativeBeforePrefix
10
- ? `-${prefix}${baseClass.slice(1)}`
11
- : `${prefix}${baseClass}`
12
- })
13
- }).processSync(selector)
16
+ if (prefix === '') {
17
+ return selector
18
+ }
19
+
20
+ let ast = typeof selector === 'string' ? parser().astSync(selector) : selector
21
+
22
+ ast.walkClasses((classSelector) => {
23
+ let baseClass = classSelector.value
24
+ let shouldPlaceNegativeBeforePrefix = prependNegative && baseClass.startsWith('-')
25
+
26
+ classSelector.value = shouldPlaceNegativeBeforePrefix
27
+ ? `-${prefix}${baseClass.slice(1)}`
28
+ : `${prefix}${baseClass}`
29
+ })
30
+
31
+ return typeof selector === 'string' ? ast.toString() : ast
14
32
  }
@@ -1,7 +1,6 @@
1
1
  import negateValue from './negateValue'
2
2
  import corePluginList from '../corePluginList'
3
3
  import configurePlugins from './configurePlugins'
4
- import defaultConfig from '../../stubs/defaultConfig.stub'
5
4
  import colors from '../public/colors'
6
5
  import { defaults } from './defaults'
7
6
  import { toPath } from './toPath'
@@ -260,7 +259,6 @@ export default function resolveConfig(configs) {
260
259
  prefix: '',
261
260
  important: false,
262
261
  separator: ':',
263
- variantOrder: defaultConfig.variantOrder,
264
262
  },
265
263
  ]
266
264