tailwindcss 3.1.2 → 3.1.5

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.
@@ -0,0 +1,27 @@
1
+ export function union(types) {
2
+ return [...new Set(types)].join(' | ')
3
+ }
4
+
5
+ export function unionValues(values) {
6
+ return union(values.map(forValue))
7
+ }
8
+
9
+ export function forKeys(value) {
10
+ return union(Object.keys(value).map((key) => `'${key}'`))
11
+ }
12
+
13
+ export function forValue(value) {
14
+ if (Array.isArray(value)) {
15
+ return `(${unionValues(value)})[]`
16
+ }
17
+
18
+ if (typeof value === 'object') {
19
+ return `Record<${forKeys(value)}, ${unionValues(Object.values(value))}>`
20
+ }
21
+
22
+ if (typeof value === 'string') {
23
+ return `string`
24
+ }
25
+
26
+ return `any`
27
+ }
@@ -14,6 +14,7 @@ import { version as tailwindVersion } from '../package.json'
14
14
  import log from './util/log'
15
15
  import { normalizeScreens } from './util/normalizeScreens'
16
16
  import { formatBoxShadowValue, parseBoxShadowValue } from './util/parseBoxShadowValue'
17
+ import { removeAlphaVariables } from './util/removeAlphaVariables'
17
18
  import { flagEnabled } from './featureFlags'
18
19
 
19
20
  export let variantPlugins = {
@@ -21,7 +22,19 @@ export let variantPlugins = {
21
22
  addVariant('first-letter', '&::first-letter')
22
23
  addVariant('first-line', '&::first-line')
23
24
 
24
- addVariant('marker', ['& *::marker', '&::marker'])
25
+ addVariant('marker', [
26
+ ({ container }) => {
27
+ removeAlphaVariables(container, ['--tw-text-opacity'])
28
+
29
+ return '& *::marker'
30
+ },
31
+ ({ container }) => {
32
+ removeAlphaVariables(container, ['--tw-text-opacity'])
33
+
34
+ return '&::marker'
35
+ },
36
+ ])
37
+
25
38
  addVariant('selection', ['& *::selection', '&::selection'])
26
39
 
27
40
  addVariant('file', '&::file-selector-button')
@@ -77,21 +90,11 @@ export let variantPlugins = {
77
90
  [
78
91
  'visited',
79
92
  ({ container }) => {
80
- let toRemove = ['--tw-text-opacity', '--tw-border-opacity', '--tw-bg-opacity']
81
-
82
- container.walkDecls((decl) => {
83
- if (toRemove.includes(decl.prop)) {
84
- decl.remove()
85
-
86
- return
87
- }
88
-
89
- for (const varName of toRemove) {
90
- if (decl.value.includes(`/ var(${varName})`)) {
91
- decl.value = decl.value.replace(`/ var(${varName})`, '')
92
- }
93
- }
94
- })
93
+ removeAlphaVariables(container, [
94
+ '--tw-text-opacity',
95
+ '--tw-border-opacity',
96
+ '--tw-bg-opacity',
97
+ ])
95
98
 
96
99
  return '&:visited'
97
100
  },
@@ -1610,7 +1613,7 @@ export let corePlugins = {
1610
1613
  {
1611
1614
  text: (value) => {
1612
1615
  let [fontSize, options] = Array.isArray(value) ? value : [value]
1613
- let { lineHeight, letterSpacing } = isPlainObject(options)
1616
+ let { lineHeight, letterSpacing, fontWeight } = isPlainObject(options)
1614
1617
  ? options
1615
1618
  : { lineHeight: options }
1616
1619
 
@@ -1618,6 +1621,7 @@ export let corePlugins = {
1618
1621
  'font-size': fontSize,
1619
1622
  ...(lineHeight === undefined ? {} : { 'line-height': lineHeight }),
1620
1623
  ...(letterSpacing === undefined ? {} : { 'letter-spacing': letterSpacing }),
1624
+ ...(fontWeight === undefined ? {} : { 'font-weight': fontWeight }),
1621
1625
  }
1622
1626
  },
1623
1627
  },
@@ -1,4 +1,4 @@
1
- import { flagEnabled } from '../featureFlags.js'
1
+ import { flagEnabled } from '../featureFlags'
2
2
  import * as regex from './regex'
3
3
 
4
4
  export function defaultExtractor(context) {
@@ -12,7 +12,7 @@ export function defaultExtractor(context) {
12
12
  let results = []
13
13
 
14
14
  for (let pattern of patterns) {
15
- results.push(...(content.match(pattern) ?? []))
15
+ results = [...results, ...(content.match(pattern) ?? [])]
16
16
  }
17
17
 
18
18
  return results.filter((v) => v !== undefined).map(clipAtBalancedParens)
@@ -22,6 +22,10 @@ export function defaultExtractor(context) {
22
22
  function* buildRegExps(context) {
23
23
  let separator = context.tailwindConfig.separator
24
24
  let variantGroupingEnabled = flagEnabled(context.tailwindConfig, 'variantGrouping')
25
+ let prefix =
26
+ context.tailwindConfig.prefix !== ''
27
+ ? regex.optional(regex.pattern([/-?/, regex.escape(context.tailwindConfig.prefix)]))
28
+ : ''
25
29
 
26
30
  let utility = regex.any([
27
31
  // Arbitrary properties
@@ -37,7 +41,7 @@ function* buildRegExps(context) {
37
41
  regex.any([
38
42
  regex.pattern([
39
43
  // Arbitrary values
40
- /-\[[^\s:]+\]/,
44
+ /-(?:\w+-)*\[[^\s:]+\]/,
41
45
 
42
46
  // Not immediately followed by an `{[(`
43
47
  /(?![{([]])/,
@@ -48,7 +52,7 @@ function* buildRegExps(context) {
48
52
 
49
53
  regex.pattern([
50
54
  // Arbitrary values
51
- /-\[[^\s]+\]/,
55
+ /-(?:\w+-)*\[[^\s]+\]/,
52
56
 
53
57
  // Not immediately followed by an `{[(`
54
58
  /(?![{([]])/,
@@ -64,31 +68,43 @@ function* buildRegExps(context) {
64
68
  ]),
65
69
  ])
66
70
 
67
- yield regex.pattern([
68
- // Variants
69
- '((?=((',
70
- regex.any(
71
- [
72
- regex.pattern([/([^\s"'`\[\\]+-)?\[[^\s"'`]+\]/, separator]),
73
- regex.pattern([/[^\s"'`\[\\]+/, separator]),
74
- ],
75
- true
76
- ),
77
- ')+))\\2)?',
78
-
79
- // Important (optional)
80
- /!?/,
81
-
82
- variantGroupingEnabled
83
- ? regex.any([
84
- // Or any of those things but grouped separated by commas
85
- regex.pattern([/\(/, utility, regex.zeroOrMore([/,/, utility]), /\)/]),
86
-
87
- // Arbitrary properties, constrained utilities, arbitrary values, etc…
88
- utility,
89
- ])
90
- : utility,
91
- ])
71
+ let variantPatterns = [
72
+ // Without quotes
73
+ regex.any([
74
+ regex.pattern([/([^\s"'`\[\\]+-)?\[[^\s"'`]+\]/, separator]),
75
+ regex.pattern([/[^\s"'`\[\\]+/, separator]),
76
+ ]),
77
+
78
+ // With quotes allowed
79
+ regex.any([
80
+ regex.pattern([/([^\s"'`\[\\]+-)?\[[^\s`]+\]/, separator]),
81
+ regex.pattern([/[^\s`\[\\]+/, separator]),
82
+ ]),
83
+ ]
84
+
85
+ for (const variantPattern of variantPatterns) {
86
+ yield regex.pattern([
87
+ // Variants
88
+ '((?=((',
89
+ variantPattern,
90
+ ')+))\\2)?',
91
+
92
+ // Important (optional)
93
+ /!?/,
94
+
95
+ prefix,
96
+
97
+ variantGroupingEnabled
98
+ ? regex.any([
99
+ // Or any of those things but grouped separated by commas
100
+ regex.pattern([/\(/, utility, regex.zeroOrMore([/,/, utility]), /\)/]),
101
+
102
+ // Arbitrary properties, constrained utilities, arbitrary values, etc…
103
+ utility,
104
+ ])
105
+ : utility,
106
+ ])
107
+ }
92
108
 
93
109
  // 5. Inner matches
94
110
  yield /[^<>"'`\s.(){}[\]#=%$]*[^<>"'`\s.(){}[\]#=%:$]/g
@@ -40,9 +40,7 @@ function listKeys(obj) {
40
40
  }
41
41
 
42
42
  function validatePath(config, path, defaultValue, themeOpts = {}) {
43
- const pathString = Array.isArray(path)
44
- ? pathToString(path)
45
- : path.replace(/^['"]+/g, '').replace(/['"]+$/g, '')
43
+ const pathString = Array.isArray(path) ? pathToString(path) : path.replace(/^['"]+|['"]+$/g, '')
46
44
  const pathSegments = Array.isArray(path) ? path : toPath(pathString)
47
45
  const value = dlv(config.theme, pathSegments, defaultValue)
48
46
 
@@ -162,6 +160,10 @@ let nodeTypePropertyMap = {
162
160
  export default function ({ tailwindConfig: config }) {
163
161
  let functions = {
164
162
  theme: (node, path, ...defaultValue) => {
163
+ // Strip quotes from beginning and end of string
164
+ // This allows the alpha value to be present inside of quotes
165
+ path = path.replace(/^['"]+|['"]+$/g, '')
166
+
165
167
  let matches = path.match(/^([^\s]+)(?![^\[]*\])(?:\s*\/\s*([^\/\s]+))$/)
166
168
  let alpha = undefined
167
169
 
@@ -181,9 +183,15 @@ export default function ({ tailwindConfig: config }) {
181
183
  throw node.error(error)
182
184
  }
183
185
 
184
- if (alpha !== undefined) {
185
- value = parseColorFormat(value)
186
- value = withAlphaValue(value, alpha, value)
186
+ let maybeColor = parseColorFormat(value)
187
+ let isColorFunction = maybeColor !== undefined && typeof maybeColor === 'function'
188
+
189
+ if (alpha !== undefined || isColorFunction) {
190
+ if (alpha === undefined) {
191
+ alpha = 1.0
192
+ }
193
+
194
+ value = withAlphaValue(maybeColor, alpha, maybeColor)
187
195
  }
188
196
 
189
197
  return value
@@ -129,6 +129,7 @@ function applyVariant(variant, matches, context) {
129
129
  }
130
130
 
131
131
  let args
132
+ let isArbitraryVariant = false
132
133
 
133
134
  // Find partial arbitrary variants
134
135
  if (variant.endsWith(']') && !variant.startsWith('[')) {
@@ -144,6 +145,8 @@ function applyVariant(variant, matches, context) {
144
145
  return []
145
146
  }
146
147
 
148
+ isArbitraryVariant = true
149
+
147
150
  let fn = parseVariant(selector)
148
151
 
149
152
  let sort = Array.from(context.variantOrder.values()).pop() << 1n
@@ -163,15 +166,17 @@ function applyVariant(variant, matches, context) {
163
166
 
164
167
  let container = postcss.root({ nodes: [rule.clone()] })
165
168
 
166
- for (let [variantSort, variantFunction] of variantFunctionTuples) {
167
- let clone = container.clone()
169
+ for (let [variantSort, variantFunction, containerFromArray] of variantFunctionTuples) {
170
+ let clone = containerFromArray ?? container.clone()
168
171
  let collectedFormats = []
169
172
 
170
- let originals = new Map()
171
-
172
173
  function prepareBackup() {
173
- if (originals.size > 0) return // Already prepared, chicken out
174
- clone.walkRules((rule) => originals.set(rule, rule.selector))
174
+ // Already prepared, chicken out
175
+ if (clone.raws.neededBackup) {
176
+ return
177
+ }
178
+ clone.raws.neededBackup = true
179
+ clone.walkRules((rule) => (rule.raws.originalSelector = rule.selector))
175
180
  }
176
181
 
177
182
  function modifySelectors(modifierFunction) {
@@ -231,6 +236,10 @@ function applyVariant(variant, matches, context) {
231
236
  // reserving additional X places for these 'unknown' variants in between.
232
237
  variantSort | BigInt(idx << ruleWithVariant.length),
233
238
  variantFunction,
239
+
240
+ // If the clone has been modified we have to pass that back
241
+ // though so each rule can use the modified container
242
+ clone.clone(),
234
243
  ])
235
244
  }
236
245
  continue
@@ -244,13 +253,15 @@ function applyVariant(variant, matches, context) {
244
253
  continue
245
254
  }
246
255
 
247
- // We filled the `originals`, therefore we assume that somebody touched
256
+ // We had to backup selectors, therefore we assume that somebody touched
248
257
  // `container` or `modifySelectors`. Let's see if they did, so that we
249
258
  // can restore the selectors, and collect the format strings.
250
- if (originals.size > 0) {
259
+ if (clone.raws.neededBackup) {
260
+ delete clone.raws.neededBackup
251
261
  clone.walkRules((rule) => {
252
- if (!originals.has(rule)) return
253
- let before = originals.get(rule)
262
+ let before = rule.raws.originalSelector
263
+ if (!before) return
264
+ delete rule.raws.originalSelector
254
265
  if (before === rule.selector) return // No mutation happened
255
266
 
256
267
  let modified = rule.selector
@@ -292,6 +303,7 @@ function applyVariant(variant, matches, context) {
292
303
  ...meta,
293
304
  sort: variantSort | meta.sort,
294
305
  collectedFormats: (meta.collectedFormats ?? []).concat(collectedFormats),
306
+ isArbitraryVariant,
295
307
  },
296
308
  clone.nodes[0],
297
309
  ]
@@ -619,6 +631,7 @@ function* resolveMatches(candidate, context, original = candidate) {
619
631
  base: candidate
620
632
  .split(new RegExp(`\\${context?.tailwindConfig?.separator ?? ':'}(?![^[]*\\])`))
621
633
  .pop(),
634
+ isArbitraryVariant: match[0].isArbitraryVariant,
622
635
 
623
636
  context,
624
637
  })
@@ -465,11 +465,14 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
465
465
  }
466
466
 
467
467
  if (Array.isArray(result)) {
468
- return result.map((variant) => parseVariant(variant))
468
+ return result
469
+ .filter((variant) => typeof variant === 'string')
470
+ .map((variant) => parseVariant(variant))
469
471
  }
470
472
 
471
473
  // result may be undefined with legacy variants that use APIs like `modifySelectors`
472
- return result && parseVariant(result)(api)
474
+ // result may also be a postcss node if someone was returning the result from `modifySelectors`
475
+ return result && typeof result === 'string' && parseVariant(result)(api)
473
476
  }
474
477
  }
475
478
 
@@ -759,6 +762,17 @@ function registerPlugins(plugins, context) {
759
762
  ]
760
763
  }
761
764
 
765
+ if ([].concat(options?.type).includes('color')) {
766
+ classes = [
767
+ ...classes,
768
+ ...classes.flatMap((cls) =>
769
+ Object.keys(context.tailwindConfig.theme.opacity).map(
770
+ (opacity) => `${cls}/${opacity}`
771
+ )
772
+ ),
773
+ ]
774
+ }
775
+
762
776
  return classes
763
777
  })()
764
778
  : [util]
@@ -40,18 +40,16 @@ export function normalize(value, isRoot = true) {
40
40
  value = value.trim()
41
41
  }
42
42
 
43
- // Add spaces around operators inside calc() that do not follow an operator
43
+ // Add spaces around operators inside math functions like calc() that do not follow an operator
44
44
  // or '('.
45
- value = value.replace(/calc\(.+\)/g, (match) => {
45
+ value = value.replace(/(calc|min|max|clamp)\(.+\)/g, (match) => {
46
46
  return match.replace(
47
47
  /(-?\d*\.?\d(?!\b-.+[,)](?![^+\-/*])\D)(?:%|[a-z]+)?|\))([+\-/*])/g,
48
48
  '$1 $2 '
49
49
  )
50
50
  })
51
51
 
52
- // Add spaces around some operators not inside calc() that do not follow an operator
53
- // or '('.
54
- return value.replace(/(-?\d*\.?\d(?!\b-.+[,)](?![^+\-/*])\D)(?:%|[a-z]+)?|\))([\/])/g, '$1 $2 ')
52
+ return value
55
53
  }
56
54
 
57
55
  export function url(value) {
@@ -35,6 +35,7 @@ export function finalizeSelector(
35
35
  selector,
36
36
  candidate,
37
37
  context,
38
+ isArbitraryVariant,
38
39
 
39
40
  // Split by the separator, but ignore the separator inside square brackets:
40
41
  //
@@ -50,7 +51,8 @@ export function finalizeSelector(
50
51
  ) {
51
52
  let ast = selectorParser().astSync(selector)
52
53
 
53
- if (context?.tailwindConfig?.prefix) {
54
+ // We explicitly DO NOT prefix classes in arbitrary variants
55
+ if (context?.tailwindConfig?.prefix && !isArbitraryVariant) {
54
56
  format = prefixSelector(context.tailwindConfig.prefix, format)
55
57
  }
56
58
 
@@ -0,0 +1,24 @@
1
+ /**
2
+ * This function removes any uses of CSS variables used as an alpha channel
3
+ *
4
+ * This is required for selectors like `:visited` which do not allow
5
+ * changes in opacity or external control using CSS variables.
6
+ *
7
+ * @param {import('postcss').Container} container
8
+ * @param {string[]} toRemove
9
+ */
10
+ export function removeAlphaVariables(container, toRemove) {
11
+ container.walkDecls((decl) => {
12
+ if (toRemove.includes(decl.prop)) {
13
+ decl.remove()
14
+
15
+ return
16
+ }
17
+
18
+ for (let varName of toRemove) {
19
+ if (decl.value.includes(`/ var(${varName})`)) {
20
+ decl.value = decl.value.replace(`/ var(${varName})`, '')
21
+ }
22
+ }
23
+ })
24
+ }
package/types/config.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { CorePluginList } from './generated/CorePluginList'
1
+ import type { CorePluginList } from './generated/corePluginList'
2
2
  import type { DefaultColors } from './generated/colors'
3
3
 
4
4
  // Helpers
@@ -12,6 +12,7 @@ interface RecursiveKeyValuePair<K extends keyof any = string, V = string> {
12
12
  [key: string]: V | RecursiveKeyValuePair<K, V>
13
13
  }
14
14
  type ResolvableTo<T> = T | ((utils: PluginUtils) => T)
15
+ type CSSRuleObject = RecursiveKeyValuePair<string, string | string[]>
15
16
 
16
17
  interface PluginUtils {
17
18
  colors: DefaultColors
@@ -163,6 +164,7 @@ interface ThemeConfig {
163
164
  configuration: Partial<{
164
165
  lineHeight: string
165
166
  letterSpacing: string
167
+ fontWeight: string | number
166
168
  }>
167
169
  ]
168
170
  >
@@ -242,7 +244,7 @@ type ValueType =
242
244
  export interface PluginAPI {
243
245
  // for registering new static utility styles
244
246
  addUtilities(
245
- utilities: RecursiveKeyValuePair | RecursiveKeyValuePair[],
247
+ utilities: CSSRuleObject | CSSRuleObject[],
246
248
  options?: Partial<{
247
249
  respectPrefix: boolean
248
250
  respectImportant: boolean
@@ -250,7 +252,7 @@ export interface PluginAPI {
250
252
  ): void
251
253
  // for registering new dynamic utility styles
252
254
  matchUtilities<T>(
253
- utilities: KeyValuePair<string, (value: T) => RecursiveKeyValuePair>,
255
+ utilities: KeyValuePair<string, (value: T) => CSSRuleObject>,
254
256
  options?: Partial<{
255
257
  respectPrefix: boolean
256
258
  respectImportant: boolean
@@ -261,7 +263,7 @@ export interface PluginAPI {
261
263
  ): void
262
264
  // for registering new static component styles
263
265
  addComponents(
264
- components: RecursiveKeyValuePair | RecursiveKeyValuePair[],
266
+ components: CSSRuleObject | CSSRuleObject[],
265
267
  options?: Partial<{
266
268
  respectPrefix: boolean
267
269
  respectImportant: boolean
@@ -269,7 +271,7 @@ export interface PluginAPI {
269
271
  ): void
270
272
  // for registering new dynamic component styles
271
273
  matchComponents<T>(
272
- components: KeyValuePair<string, (value: T) => RecursiveKeyValuePair>,
274
+ components: KeyValuePair<string, (value: T) => CSSRuleObject>,
273
275
  options?: Partial<{
274
276
  respectPrefix: boolean
275
277
  respectImportant: boolean
@@ -279,7 +281,7 @@ export interface PluginAPI {
279
281
  }>
280
282
  ): void
281
283
  // for registering new base styles
282
- addBase(base: RecursiveKeyValuePair | RecursiveKeyValuePair[]): void
284
+ addBase(base: CSSRuleObject | CSSRuleObject[]): void
283
285
  // for registering custom variants
284
286
  addVariant(name: string, definition: string | string[] | (() => string) | (() => string)[]): void
285
287
  // for looking up values in the user’s theme configuration