tailwindcss 3.0.18 → 3.0.22

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.
@@ -9,7 +9,7 @@
9
9
  box-sizing: border-box; /* 1 */
10
10
  border-width: 0; /* 2 */
11
11
  border-style: solid; /* 2 */
12
- border-color: theme('borderColor.DEFAULT', 'currentColor'); /* 2 */
12
+ border-color: theme('borderColor.DEFAULT', currentColor); /* 2 */
13
13
  }
14
14
 
15
15
  ::before,
@@ -161,12 +161,12 @@ function processApply(root, context) {
161
161
  }
162
162
 
163
163
  for (let applyCandidate of applyCandidates) {
164
- if (!applyClassCache.has(applyCandidate)) {
165
- if (applyCandidate === prefix(context, 'group')) {
166
- // TODO: Link to specific documentation page with error code.
167
- throw apply.error(`@apply should not be used with the '${applyCandidate}' utility`)
168
- }
164
+ if ([prefix(context, 'group'), prefix(context, 'peer')].includes(applyCandidate)) {
165
+ // TODO: Link to specific documentation page with error code.
166
+ throw apply.error(`@apply should not be used with the '${applyCandidate}' utility`)
167
+ }
169
168
 
169
+ if (!applyClassCache.has(applyCandidate)) {
170
170
  throw apply.error(
171
171
  `The \`${applyCandidate}\` class does not exist. If \`${applyCandidate}\` is a custom class, make sure it is defined within a \`@layer\` directive.`
172
172
  )
@@ -25,33 +25,33 @@ function getClassNameFromSelector(selector) {
25
25
  // Example with dynamic classes:
26
26
  // ['grid-cols', '[[linename],1fr,auto]']
27
27
  // ['grid', 'cols-[[linename],1fr,auto]']
28
- function* candidatePermutations(candidate, lastIndex = Infinity) {
29
- if (lastIndex < 0) {
30
- return
31
- }
28
+ function* candidatePermutations(candidate) {
29
+ let lastIndex = Infinity
32
30
 
33
- let dashIdx
31
+ while (lastIndex >= 0) {
32
+ let dashIdx
34
33
 
35
- if (lastIndex === Infinity && candidate.endsWith(']')) {
36
- let bracketIdx = candidate.indexOf('[')
34
+ if (lastIndex === Infinity && candidate.endsWith(']')) {
35
+ let bracketIdx = candidate.indexOf('[')
37
36
 
38
- // If character before `[` isn't a dash or a slash, this isn't a dynamic class
39
- // eg. string[]
40
- dashIdx = ['-', '/'].includes(candidate[bracketIdx - 1]) ? bracketIdx - 1 : -1
41
- } else {
42
- dashIdx = candidate.lastIndexOf('-', lastIndex)
43
- }
37
+ // If character before `[` isn't a dash or a slash, this isn't a dynamic class
38
+ // eg. string[]
39
+ dashIdx = ['-', '/'].includes(candidate[bracketIdx - 1]) ? bracketIdx - 1 : -1
40
+ } else {
41
+ dashIdx = candidate.lastIndexOf('-', lastIndex)
42
+ }
44
43
 
45
- if (dashIdx < 0) {
46
- return
47
- }
44
+ if (dashIdx < 0) {
45
+ break
46
+ }
48
47
 
49
- let prefix = candidate.slice(0, dashIdx)
50
- let modifier = candidate.slice(dashIdx + 1)
48
+ let prefix = candidate.slice(0, dashIdx)
49
+ let modifier = candidate.slice(dashIdx + 1)
51
50
 
52
- yield [prefix, modifier]
51
+ yield [prefix, modifier]
53
52
 
54
- yield* candidatePermutations(candidate, dashIdx - 1)
53
+ lastIndex = dashIdx - 1
54
+ }
55
55
  }
56
56
 
57
57
  function applyPrefix(matches, context) {
@@ -63,9 +63,23 @@ function applyPrefix(matches, context) {
63
63
  let [meta] = match
64
64
  if (meta.options.respectPrefix) {
65
65
  let container = postcss.root({ nodes: [match[1].clone()] })
66
+ let classCandidate = match[1].raws.tailwind.classCandidate
67
+
66
68
  container.walkRules((r) => {
67
- r.selector = prefixSelector(context.tailwindConfig.prefix, r.selector)
69
+ // If this is a negative utility with a dash *before* the prefix we
70
+ // have to ensure that the generated selector matches the candidate
71
+
72
+ // Not doing this will cause `-tw-top-1` to generate the class `.tw--top-1`
73
+ // The disconnect between candidate <-> class can cause @apply to hard crash.
74
+ let shouldPrependNegative = classCandidate.startsWith('-')
75
+
76
+ r.selector = prefixSelector(
77
+ context.tailwindConfig.prefix,
78
+ r.selector,
79
+ shouldPrependNegative
80
+ )
68
81
  })
82
+
69
83
  match[1] = container.nodes[0]
70
84
  }
71
85
  }
@@ -220,7 +234,7 @@ function applyVariant(variant, matches, context) {
220
234
  // For example:
221
235
  // .sm:underline {} is a variant of something in the utilities layer
222
236
  // .sm:container {} is a variant of the container component
223
- clone.nodes[0].raws.tailwind = { parentLayer: meta.layer }
237
+ clone.nodes[0].raws.tailwind = { ...clone.nodes[0].raws.tailwind, parentLayer: meta.layer }
224
238
 
225
239
  let withOffset = [
226
240
  {
@@ -262,11 +276,37 @@ function parseRules(rule, cache, options = {}) {
262
276
  const IS_VALID_PROPERTY_NAME = /^[a-z_-]/
263
277
 
264
278
  function isValidPropName(name) {
265
- // TODO: properly fix this!
266
- return IS_VALID_PROPERTY_NAME.test(name) && !name.startsWith('http')
279
+ return IS_VALID_PROPERTY_NAME.test(name)
280
+ }
281
+
282
+ /**
283
+ * @param {string} declaration
284
+ * @returns {boolean}
285
+ */
286
+ function looksLikeUri(declaration) {
287
+ // Quick bailout for obvious non-urls
288
+ // This doesn't support schemes that don't use a leading // but that's unlikely to be a problem
289
+ if (!declaration.includes('://')) {
290
+ return false
291
+ }
292
+
293
+ try {
294
+ const url = new URL(declaration)
295
+ return url.scheme !== '' && url.host !== ''
296
+ } catch (err) {
297
+ // Definitely not a valid url
298
+ return false
299
+ }
267
300
  }
268
301
 
269
302
  function isParsableCssValue(property, value) {
303
+ // We don't want to to treat [https://example.com] as a custom property
304
+ // Even though, according to the CSS grammar, it's a totally valid CSS declaration
305
+ // So we short-circuit here by checking if the custom property looks like a url
306
+ if (looksLikeUri(`${property}:${value}`)) {
307
+ return false
308
+ }
309
+
270
310
  try {
271
311
  postcss.parse(`a{${property}:${value}}`).toResult()
272
312
  return true
@@ -345,6 +385,14 @@ function splitWithSeparator(input, separator) {
345
385
  return input.split(new RegExp(`\\${separator}(?![^[]*\\])`, 'g'))
346
386
  }
347
387
 
388
+ function* recordCandidates(matches, classCandidate) {
389
+ for (const match of matches) {
390
+ match[1].raws.tailwind = { ...match[1].raws.tailwind, classCandidate }
391
+
392
+ yield match
393
+ }
394
+ }
395
+
348
396
  function* resolveMatches(candidate, context) {
349
397
  let separator = context.tailwindConfig.separator
350
398
  let [classCandidate, ...variants] = splitWithSeparator(candidate, separator).reverse()
@@ -456,7 +504,9 @@ function* resolveMatches(candidate, context) {
456
504
  continue
457
505
  }
458
506
 
459
- matches = applyPrefix(matches.flat(), context)
507
+ matches = matches.flat()
508
+ matches = Array.from(recordCandidates(matches, classCandidate))
509
+ matches = applyPrefix(matches, context)
460
510
 
461
511
  if (important) {
462
512
  matches = applyImportant(matches, context)
@@ -467,6 +517,8 @@ function* resolveMatches(candidate, context) {
467
517
  }
468
518
 
469
519
  for (let match of matches) {
520
+ match[1].raws.tailwind = { ...match[1].raws.tailwind, candidate }
521
+
470
522
  // Apply final format selector
471
523
  if (match[0].collectedFormats) {
472
524
  let finalFormat = formatVariantSelector('&', ...match[0].collectedFormats)
@@ -19,6 +19,12 @@ import { toPath } from '../util/toPath'
19
19
  import log from '../util/log'
20
20
  import negateValue from '../util/negateValue'
21
21
  import isValidArbitraryValue from '../util/isValidArbitraryValue'
22
+ import { generateRules } from './generateRules'
23
+
24
+ function prefix(context, selector) {
25
+ let prefix = context.tailwindConfig.prefix
26
+ return typeof prefix === 'function' ? prefix(selector) : prefix + selector
27
+ }
22
28
 
23
29
  function parseVariantFormatString(input) {
24
30
  if (input.includes('{')) {
@@ -666,17 +672,30 @@ function registerPlugins(plugins, context) {
666
672
 
667
673
  if (checks.length > 0) {
668
674
  let patternMatchingCount = new Map()
675
+ let prefixLength = context.tailwindConfig.prefix.length
669
676
 
670
677
  for (let util of classList) {
671
678
  let utils = Array.isArray(util)
672
679
  ? (() => {
673
680
  let [utilName, options] = util
674
- let classes = Object.keys(options?.values ?? {}).map((value) =>
675
- formatClass(utilName, value)
676
- )
681
+ let values = Object.keys(options?.values ?? {})
682
+ let classes = values.map((value) => formatClass(utilName, value))
677
683
 
678
684
  if (options?.supportsNegativeValues) {
685
+ // This is the normal negated version
686
+ // e.g. `-inset-1` or `-tw-inset-1`
679
687
  classes = [...classes, ...classes.map((cls) => '-' + cls)]
688
+
689
+ // This is the negated version *after* the prefix
690
+ // e.g. `tw--inset-1`
691
+ // The prefix is already attached to util name
692
+ // So we add the negative after the prefix
693
+ classes = [
694
+ ...classes,
695
+ ...classes.map(
696
+ (cls) => cls.slice(0, prefixLength) + '-' + cls.slice(prefixLength)
697
+ ),
698
+ ]
680
699
  }
681
700
 
682
701
  return classes
@@ -720,9 +739,43 @@ function registerPlugins(plugins, context) {
720
739
  }
721
740
  }
722
741
 
742
+ // A list of utilities that are used by certain Tailwind CSS utilities but
743
+ // that don't exist on their own. This will result in them "not existing" and
744
+ // sorting could be weird since you still require them in order to make the
745
+ // host utitlies work properly. (Thanks Biology)
746
+ let parasiteUtilities = new Set([prefix(context, 'group'), prefix(context, 'peer')])
747
+ context.sortClassList = function sortClassList(classes) {
748
+ let sortedClassNames = new Map()
749
+ for (let [sort, rule] of generateRules(new Set(classes), context)) {
750
+ if (sortedClassNames.has(rule.raws.tailwind.candidate)) continue
751
+ sortedClassNames.set(rule.raws.tailwind.candidate, sort)
752
+ }
753
+
754
+ return classes
755
+ .map((className) => {
756
+ let order = sortedClassNames.get(className) ?? null
757
+
758
+ if (order === null && parasiteUtilities.has(className)) {
759
+ // This will make sure that it is at the very beginning of the
760
+ // `components` layer which technically means 'before any
761
+ // components'.
762
+ order = context.layerOrder.components
763
+ }
764
+
765
+ return [className, order]
766
+ })
767
+ .sort(([, a], [, z]) => {
768
+ if (a === z) return 0
769
+ if (a === null) return -1
770
+ if (z === null) return 1
771
+ return bigSign(a - z)
772
+ })
773
+ .map(([className]) => className)
774
+ }
775
+
723
776
  // Generate a list of strings for autocompletion purposes, e.g.
724
777
  // ['uppercase', 'lowercase', ...]
725
- context.getClassList = function () {
778
+ context.getClassList = function getClassList() {
726
779
  let output = []
727
780
 
728
781
  for (let util of classList) {
@@ -1,7 +1,7 @@
1
1
  let KEYWORDS = new Set(['inset', 'inherit', 'initial', 'revert', 'unset'])
2
2
  let COMMA = /\,(?![^(]*\))/g // Comma separator that is not located between brackets. E.g.: `cubiz-bezier(a, b, c)` these don't count.
3
3
  let SPACE = /\ +(?![^(]*\))/g // Similar to the one above, but with spaces instead.
4
- let LENGTH = /^-?(\d+)(.*?)$/g
4
+ let LENGTH = /^-?(\d+|\.\d+)(.*?)$/g
5
5
 
6
6
  export function parseBoxShadowValue(input) {
7
7
  let shadows = input.split(COMMA)
@@ -1,12 +1,14 @@
1
1
  import parser from 'postcss-selector-parser'
2
- import { tap } from './tap'
3
2
 
4
- export default function (prefix, selector) {
3
+ export default function (prefix, selector, prependNegative = false) {
5
4
  return parser((selectors) => {
6
5
  selectors.walkClasses((classSelector) => {
7
- tap(classSelector.value, (baseClass) => {
8
- classSelector.value = `${prefix}${baseClass}`
9
- })
6
+ let baseClass = classSelector.value
7
+ let shouldPlaceNegativeBeforePrefix = prependNegative && baseClass.startsWith('-')
8
+
9
+ classSelector.value = shouldPlaceNegativeBeforePrefix
10
+ ? `-${prefix}${baseClass.slice(1)}`
11
+ : `${prefix}${baseClass}`
10
12
  })
11
13
  }).processSync(selector)
12
14
  }